# Search

Package: @commenda/pathfinder@0.1.0
Page: /docs/components/search
Figma: https://www.figma.com/design/dn7BkGYEfIyP0o2zfesfVh/The-System?node-id=635-13518

```tsx
import { Search } from '@commenda/pathfinder/search';

export const SearchDefault = () => (
  <Search label="Search filings" hideLabel placeholder="Search filings" />
);
```


## Installation [#installation]

Import the component in your screen:

```tsx
import { Search } from '@commenda/pathfinder/search';
```

Load the stylesheet once in your app root, after host styles:

```tsx
import '@commenda/pathfinder/styles.css';
```

Pathfinder ships precompiled CSS; no Tailwind setup is required. In Next.js App Router, put interactive state and callbacks in a client component. See [installation](/docs/getting-started/installation) for the workspace setup.

## API reference [#api-reference]

| Prop | Type | Requirement | Behavior |
| --- | --- | --- | --- |
| `variant` | `"fixed" \| "expandable" \| "global"` | optional | fixed; heights 28/28/40px |
| `loadingConfig` | `{ isLoading: boolean; label?: string; }` | optional | Spinner replaces search icon |
| `shortcutConfig` | `{ key: string; label: string; scopeId: string; }` | optional | Opt-in scoped shortcut; ignores typing targets |
| `onSearch` | `((query: string) => void)` | optional | Enter submission; ignored during IME composition |
| `label` | `string` | conditional | Visible field label. Supply an accessible name when it is omitted. |
| `hideLabel` | `boolean` | optional | Visually hide the label while preserving its accessible name. |
| `aria-label` | `string` | conditional | Accessible name when the field has no visible label. |
| `required` | `boolean` | conditional | Mark the field as required. See validation guidance for the component. |
| `labelConfig` | `{ optionalLabel: string; help?: never; } \| { help: string; optionalLabel?: never; }` | optional | Choose either `optionalLabel` or `help`, never both. `never` excludes the other field. |
| `description` | `string` | optional | Supporting guidance associated with the field. |
| `errorConfig` | `{ message: string; reveal?: "after-blur" \| "immediate"; }` | optional | Validation message and when it becomes visible. |
| `disableConfig` | `DisableConfig` | optional | Disabled state with an optional explanation. |
| `value` | `string` | conditional |  |
| `onValueChange` | `((value: string) => void)` | conditional |  |
| `defaultValue` | `string` | optional |  |

### loadingConfig fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `isLoading` | `boolean` | Yes |  |
| `label` | `string` | No |  |

### shortcutConfig fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `key` | `string` | Yes |  |
| `label` | `string` | Yes |  |
| `scopeId` | `string` | Yes |  |

### labelConfig fields

Choose one shape; excluded fields cannot be combined.

#### Option 1

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `optionalLabel` | `string` | Yes |  |

Do not supply: `help`.

#### Option 2

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `help` | `string` | Yes |  |

Do not supply: `optionalLabel`.

### errorConfig fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `message` | `string` | Yes |  |
| `reveal` | `"after-blur" \| "immediate"` | No |  |

### disableConfig fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `isDisabled` | `boolean` | Yes |  |
| `reason` | `string` | No |  |

Supports Input’s shared field presentation and native search-input attributes. Use hideLabel where the surrounding toolbar supplies visual context; an accessible label is still required.

## Keyboard and request behavior [#keyboard-and-request-behavior]

Application code handles debounce and requests; onValueChange is immediate. onSearch is Enter submission, not each keystroke. Escape clears the query, then blurs a fixed/global input or collapses expandable search and restores trigger focus. The clear button clears and folds expandable search. Loading is application-owned.

A result overlay handles Escape first through onKeyDown and preventDefault; the field then retains its query. Search result navigation is a documented composition, separate from selecting a saved value. Do not implement a second global shortcut listener on every instance: shortcutConfig binds only to the specified scope element and ignores editing targets.

### Variants [#variants]


```tsx
import { Search } from '@commenda/pathfinder/search';

export const SearchVariants = () => (
  <div style={{ display: 'grid', gap: 20 }}>
    <Search label="Fixed search" hideLabel placeholder="Search filings" />
    <Search
      label="Expandable search"
      hideLabel
      variant="expandable"
      placeholder="Search filings"
    />
    <Search
      label="Global search"
      hideLabel
      variant="global"
      placeholder="Search documents across all entities"
    />
  </div>
);
```


### States [#states]


```tsx
import { Search } from '@commenda/pathfinder/search';

export const SearchStates = () => (
  <div style={{ display: 'grid', gap: 20 }}>
    <Search
      label="Loading search"
      hideLabel
      defaultValue="Delaware"
      loadingConfig={{ isLoading: true }}
    />
    <Search
      label="Disabled search"
      hideLabel
      disableConfig={{
        isDisabled: true,
        reason: 'Search is unavailable while importing.',
      }}
    />
  </div>
);
```


### Results [#results]


```tsx
import { useState } from 'react';

import { Search } from '@commenda/pathfinder/search';

export const SearchResults = () => {
  const [query, setQuery] = useState('');
  const [open, setOpen] = useState(false);

  return (
    <div>
      <Search
        label="Search documents"
        hideLabel
        value={query}
        onValueChange={(next) => {
          setQuery(next);
          setOpen(true);
        }}
        onKeyDown={(event) => {
          if (event.key === 'Escape' && open) {
            event.preventDefault();
            setOpen(false);
          }
        }}
      />
      {open && query && (
        <nav aria-label="Search results">
          <ul>
            <li>
              <a href="#certificate">Certificate of incorporation</a>
            </li>
            <li>
              <a href="#report">Annual report</a>
            </li>
          </ul>
        </nav>
      )}
    </div>
  );
};
```


Read [selection and search rules](/docs/components/search#design-rules) for source provenance, API ownership, and constraints.

## Design rules [#design-rules]

Figma guidance and reviewed implementation decisions for this component. Rule IDs remain stable for source comments and AI references.

### Source rules [#source-rules]

| Rule       | Figma source         | Contract                                                                            |
| ---------- | -------------------- | ----------------------------------------------------------------------------------- |
| SEARCH-001 | 636:31822            | Fixed/expandable 28px; expandable opens to 220px; global 40px/max720.               |
| SEARCH-002 | 637:32230, 637:32236 | Expandable in table toolbar; result counts belong in table range text.              |
| SEARCH-003 | 637:32193, 637:1779  | Escape closes results, clears, then blurs/collapses. Clear folds expandable search. |

Sources: [Search](https://www.figma.com/design/dn7BkGYEfIyP0o2zfesfVh/The-System?node-id=635-13518), [Select](https://www.figma.com/design/dn7BkGYEfIyP0o2zfesfVh/The-System?node-id=179-2), [MultiSelect](https://www.figma.com/design/dn7BkGYEfIyP0o2zfesfVh/The-System?node-id=865-3077). The supplied Select node 182:1182 is its dimensions frame; 180:1177 is the actual component set.

### Accessibility and forms [#accessibility-and-forms]

Labels, descriptions, error reveal, and disable reasons follow the shared field contract. Single selection serializes one ID; multiple selection serializes repeated names. Trigger refs and form reset work for both primitive paths. Searchable popups provide an explicitly named query input. Load more and retry are keyboard-accessible buttons outside the option list.

Tag remove buttons are siblings of the trigger, never nested inside it. Long selected labels remain available through tooltips. Search result compositions must handle overlay Escape before field clear/collapse through preventDefault.

Flags, chevrons, and clear/remove icons sit in explicit square flex containers with both axes centered. Do not rely on inline SVG baseline alignment or compensate with pixel offsets. The selected value, tags, and trailing controls share the field’s vertical center. Row Checkbox instances are inert visual indicators; the option owns selection and accessibility semantics.
