# SearchSelect

```tsx
import { SearchSelect, useSearchSelect } from '@tangible/ui';
```

## SearchSelect props

| Prop | Type | Default | Description |
| - | - | - | - |
| `children` | `ReactNode` | | |
| `anchorRef?` | `React.RefObject<HTMLElement \| null>` | | EXTERNAL-TRIGGER MODE: anchor the panel to this element instead of the built-in Trigger. Use with controlled `open`/`onOpenChange` and no `<SearchSelect.Trigger>` — e.g. an IconButton that opens a picker panel under its host field. Provide `aria-label` (the panel's name — the trigger-labelled path doesn't exist here) and wire the external button's own aria-haspopup/aria-expanded. |
| `aria-describedby?` | `string` | | |
| `aria-invalid?` | `"false" \| "true" \| boolean` | | |
| `aria-label?` | `string` | | |
| `aria-labelledby?` | `string` | | |
| `defaultOpen?` | `boolean` | `false` | |
| `defaultValue?` | `OptionValue` | | Uncontrolled initial value. |
| `disabled?` | `boolean` | `false` | |
| `filter?` | `false \| ((textValue: string, query: string) => boolean)` | `case-insensitive substring match on textValue` | Built-in filtering: options whose textValue fails this test against the query unmount. Pass `false` to disable and own the filtering by rendering a different set of Options (async lists). |
| `id?` | `string` | | Trigger id — Field.Control injects this. |
| `inputValue?` | `string` | | Controlled search text. Usually uncontrolled — the query is transient and resets when the panel closes. |
| `labels?` | `SearchSelectLabels` | | |
| `onInputChange?` | `(value: string) => void` | | Fires as the user types (and on the reset-on-close). |
| `onOpenChange?` | `(open: boolean) => void` | | |
| `onValueChange?` | `(value: OptionValue) => void` | | Fires with the committed option value. |
| `open?` | `boolean` | | Controlled open state. |
| `placeholder?` | `string` | `''` | Trigger text when nothing is selected. |
| `restoreFocusRef?` | `React.RefObject<HTMLElement \| null>` | | External-trigger mode: where focus returns on commit/Escape (usually the button that opened the panel). Blur onto this element keeps the panel open, so the button's own click handler can toggle without flicker. |
| `size?` | `"lg" \| "md" \| "sm"` | `'md'` | Control size for the trigger. |
| `value?` | `OptionValue` | | Controlled selected value. |

### SearchSelect.Trigger

Also exported as `SearchSelectTrigger`.

| Prop | Type | Default | Description |
| - | - | - | - |
| `children?` | `ReactNode` | | Custom trigger content — replaces the default value/placeholder text (the chevron stays). The fields dynamic-value picker renders its bolt + type + key pill here. NOTE: with an `aria-label` on the Root the accessible name is frozen to that label — custom content is then invisible to AT (family-wide follow-up in the roadmap). |
| `className?` | `string` | | |

Also accepts the standard attributes of `ButtonHTMLAttributes` (`data-*`, `aria-*`, event handlers), passed to the element it renders.

### SearchSelect.Content

Also exported as `SearchSelectContent`.

| Prop | Type | Default | Description |
| - | - | - | - |
| `children?` | `ReactNode` | | |
| `className?` | `string` | | |

### SearchSelect.Option

Also exported as `SearchSelectOption`.

| Prop | Type | Default | Description |
| - | - | - | - |
| `value` | `OptionValue` | | |
| `children?` | `ReactNode` | | |
| `className?` | `string` | | |
| `disabled?` | `boolean` | `false` | |
| `textValue?` | `string` | | Required when children is not a plain string. |

### SearchSelect.Group

Also exported as `SearchSelectGroup`.

| Prop | Type | Default | Description |
| - | - | - | - |
| `children?` | `ReactNode` | | |
| `className?` | `string` | | |

### SearchSelect.Label

Also exported as `SearchSelectLabel`.

| Prop | Type | Default | Description |
| - | - | - | - |
| `children?` | `ReactNode` | | |
| `className?` | `string` | | |

## Hooks

### useSearchSelect

```ts
useSearchSelect(): SearchSelectContextValue
```

Public escape hatch, mirroring useSelect/useCombobox.

Returns:

| Member | Type | Description |
| - | - | - |
| `disabled` | `boolean` | |
| `placeholder` | `string` | |
| `size` | `"lg" \| "md" \| "sm"` | |
| `labels` | `Required<SearchSelectLabels>` | |
| `triggerId` | `string` | |
| `listboxId` | `string` | |
| `panelId` | `string` | |
| `ariaLabel?` | `string` | |
| `ariaLabelledBy?` | `string` | |
| `ariaDescribedBy?` | `string` | |
| `ariaInvalid?` | `"false" \| "true" \| boolean` | |
| `setOpen` | `(open: boolean) => void` | |
| `selectValue` | `(value: OptionValue, textValue: string) => void` | |
| `setInputValue` | `(value: string) => void` | |
| `registerOption` | `(option: RegisteredOption) => void` | |
| `unregisterOption` | `(optionValue: OptionValue) => void` | |
| `labelFor` | `(value: OptionValue) => string` | |
| `returnFocus` | `() => void` | Focus the trigger (or restoreFocusRef in external-trigger mode). |
| `handlePanelBlur` | `(e: React.FocusEvent) => void` | |
| `handleInputKeyDown` | `(e: React.KeyboardEvent) => void` | |
| `refs` | `{ reference: RefObject<ReferenceType \| null>; floating: RefObject<HTMLElement \| null>; setReference: (node: ReferenceType \| null) => void; setPositionReference: (node: ReferenceType \| null) => void; setFloating: (node: HTMLElement \| null) => void; }` | |
| `triggerElRef` | `MutableRefObject<HTMLButtonElement \| null>` | |
| `anchorRef?` | `React.RefObject<HTMLElement \| null>` | External-trigger mode anchor (see SearchSelectProps.anchorRef). |
| `inputRef` | `MutableRefObject<HTMLInputElement \| null>` | |
| `listRef` | `MutableRefObject<(HTMLElement \| null)[]>` | |
| `optionIndexMap` | `Map<string, number>` | |
| `context` | `FloatingContext` | |
| `getReferenceProps` | `(userProps?: React.HTMLProps<Element>) => Record<string, unknown>` | |
| `getFloatingProps` | `(userProps?: React.HTMLProps<HTMLElement>) => Record<string, unknown>` | |
| `getItemProps` | `(userProps?: React.HTMLProps<HTMLElement>) => Record<string, unknown>` | |
| `open` | `boolean` | |
| `value` | `OptionValue \| undefined` | |
| `inputValue` | `string` | |
| `query` | `string` | |
| `filter` | `false \| ((textValue: string, query: string) => boolean)` | |
| `activeIndex` | `number` | |
| `activeOptionId` | `string \| undefined` | |
| `orderedOptions` | `RegisteredOption[]` | |
| `floatingStyles` | `CSSProperties` | |

`RegisteredOption` (the `option` of `registerOption`):

| Member | Type | Description |
| - | - | - |
| `value` | `OptionValue` | |
| `ref` | `RefObject<HTMLElement \| null>` | |
| `disabled` | `boolean` | |
| `textValue` | `string` | |

## Examples

- [External trigger (anchorRef)](https://storybook-tangible-ui.pages.dev/?path=/story/forms-searchselect--external-trigger): EXTERNAL-TRIGGER MODE: no `SearchSelect.Trigger` — an IconButton opens a
  controlled panel anchored to the host field via `anchorRef`, with focus
  returning to the button via `restoreFocusRef`. The fields dynamic-value
  insert affordance is exactly this shape.

- [With Field Error](https://storybook-tangible-ui.pages.dev/?path=/story/forms-searchselect--with-field-error): SearchSelect inside Field with Field.Error — `aria-invalid` lands on the
  trigger, the element wearing `tui-input-group`, so the danger border lights.

  No `aria-required` counterpart: the trigger is `aria-haspopup="dialog"` on a
  plain button, and `role="button"` does not support that attribute. Field.Label
  carries the requirement instead.

Other stories: Default, With default value, In a Field, Custom trigger content, Sizes, Disabled.

## Component API tokens

Read via fallback, never declared. Set one on an ancestor to restyle every instance under it.

- `--tui-search-select-trigger-bg`
- `--tui-search-select-trigger-fg`
- `--tui-search-select-trigger-border`
- `--tui-search-select-trigger-border-hover`
- `--tui-search-select-trigger-border-focus`
- `--tui-search-select-trigger-radius`
- `--tui-search-select-panel-bg`
- `--tui-search-select-panel-fg`
- `--tui-search-select-panel-border`
- `--tui-search-select-panel-shadow`
- `--tui-search-select-panel-radius`
- `--tui-search-select-panel-width`
- `--tui-search-select-option-bg-hover`
- `--tui-search-select-option-bg-selected`
- `--tui-search-select-option-fg-hover`
- `--tui-search-select-option-fg-selected`
- `--tui-search-select-option-bg-selected-hover`
- `--tui-search-select-option-check`
