# Select

```tsx
import { Select, useSelect } from '@tangible/ui';
```

Interactive docs and examples: [Select in Storybook](https://storybook-tangible-ui.pages.dev/?path=/docs/forms-select--docs).

## Select props

| Prop | Type | Default | Description |
| - | - | - | - |
| `children` | `React.ReactNode` | | |
| `align?` | `"center" \| "end" \| "start"` | `'start'` | Alignment along the side. |
| `aria-describedby?` | `string` | | ID of element(s) that describe this select. |
| `aria-invalid?` | `"false" \| "true" \| boolean` | | Marks the control invalid. Lands on the trigger, which also wears `tui-input-group` — that is what lights the danger border. Injected automatically by `Field.Control` when the Field has an error. |
| `aria-label?` | `string` | | Accessible name for the select. |
| `aria-labelledby?` | `string` | | ID of element that labels this select. |
| `aria-required?` | `"false" \| "true" \| boolean` | | Marks the control required. Injected automatically by `Field.Control` when the Field is required. |
| `clearable?` | `boolean` | `false` | Whether the selection can be cleared with a clear button. |
| `clearLabel?` | `string` | `'Clear selection'` | Accessible label for the clear button (i18n). |
| `defaultOpen?` | `boolean` | `false` | Default open state for uncontrolled usage. |
| `defaultValue?` | `OptionValue` | | Default value for uncontrolled usage. |
| `disabled?` | `boolean` | `false` | Whether the entire select is disabled. |
| `id?` | `string` | | Optional trigger ID override. Useful for Field integration via htmlFor. |
| `onOpenChange?` | `(open: boolean) => void` | | Callback when open state changes. |
| `onValueChange?` | `(value: OptionValue \| undefined) => void` | | Callback when selection changes. Called with undefined when selection is cleared. |
| `open?` | `boolean` | | Controlled open state. |
| `placeholder?` | `string` | `''` | Placeholder text when no value is selected. |
| `side?` | `"bottom" \| "top"` | `'bottom'` | Preferred side of the trigger to place the listbox. |
| `sideOffset?` | `number` | `4` | Distance from the trigger in pixels. |
| `size?` | `"lg" \| "md" \| "sm"` | `'md'` | Control size. |
| `value?` | `OptionValue` | | Controlled selected value. |

### Select.Trigger

Also exported as `SelectTrigger`.

| Prop | Type | Default | Description |
| - | - | - | - |
| `asChild?` | `boolean` | `false` | When true, merges props onto the child element instead of rendering the default trigger button. Child must be a single React element that accepts ref, ARIA attributes, and event handlers (onClick, onKeyDown, onBlur, onMouseDown, onPointerDown). Note: `aria-label` and `aria-labelledby` from Select Root are spread onto the child but may be overridden by the child's own props. Ensure the child does not silently replace these if the Root provides them. |
| `children?` | `React.ReactNode` | | |
| `className?` | `string` | | Additional CSS class names. |

### Select.Content

Also exported as `SelectContent`.

| Prop | Type | Default | Description |
| - | - | - | - |
| `children` | `React.ReactNode` | | |
| `className?` | `string` | | Additional CSS class names. |

### Select.Option

Also exported as `SelectOption`.

| Prop | Type | Default | Description |
| - | - | - | - |
| `children` | `React.ReactNode` | | |
| `value` | `OptionValue` | | The value for this option. Required and must be unique. Can be string or number. |
| `className?` | `string` | | Additional CSS class names. |
| `disabled?` | `boolean` | `false` | Whether this option is disabled. |
| `textValue?` | `string` | | Text value for typeahead. Required when children is not a string. If children is a string, defaults to that. |

### Select.Group

Also exported as `SelectGroup`.

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

### Select.Label

Also exported as `SelectLabel`.

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

## Hooks

### useSelect

```ts
useSelect(): SelectContextValue
```

Access Select context: config, IDs, refs, callbacks, and reactive state.

Returns:

| Member | Type | Description |
| - | - | - |
| `disabled` | `boolean` | |
| `placeholder` | `string` | |
| `clearable` | `boolean` | |
| `clearLabel` | `string` | |
| `size` | `"lg" \| "md" \| "sm"` | |
| `triggerId` | `string` | |
| `listboxId` | `string` | |
| `ariaLabel?` | `string` | |
| `ariaLabelledBy?` | `string` | |
| `ariaDescribedBy?` | `string` | |
| `ariaInvalid?` | `"false" \| "true" \| boolean` | |
| `ariaRequired?` | `"false" \| "true" \| boolean` | |
| `setOpen` | `(open: boolean) => void` | |
| `setValue` | `(value: OptionValue \| undefined, textValue?: string) => void` | |
| `registerOption` | `(option: RegisteredOption) => void` | |
| `unregisterOption` | `(value: OptionValue) => void` | |
| `handleSelect` | `(index: number \| null) => void` | |
| `refs` | `{ reference: React.RefObject<ReferenceType \| null>; floating: React.RefObject<HTMLElement \| null>; setReference: (node: ReferenceType \| null) => void; setFloating: (node: HTMLElement \| null) => void; }` | |
| `listRef` | `MutableRefObject<(HTMLElement \| null)[]>` | |
| `justClearedRef` | `MutableRefObject<boolean>` | Guard flag — prevents Floating UI from opening dropdown during a clear |
| `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` | |
| `displayText` | `string \| undefined` | |
| `activeIndex` | `number \| null` | |
| `highlightedValue` | `OptionValue \| null` | |
| `orderedOptions` | `RegisteredOption[]` | |
| `optionIndexMap` | `Map<string, number>` | O(1) index lookup: toKey(value) → index in orderedOptions |
| `floatingStyles` | `CSSProperties` | |

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

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

## Examples

- [Controlled](https://storybook-tangible-ui.pages.dev/?path=/story/forms-select--controlled): Demonstrates controlled value with external buttons to set/clear selection.

- [Controlled Open State](https://storybook-tangible-ui.pages.dev/?path=/story/forms-select--controlled-open): Demonstrates controlled open/close state with external toggle buttons.

- [Typeahead](https://storybook-tangible-ui.pages.dev/?path=/story/forms-select--typeahead): Type characters while open to jump to matching options (e.g. "bl" → Blueberry).

- [Keyboard Navigation](https://storybook-tangible-ui.pages.dev/?path=/story/forms-select--keyboard-navigation): Shows keyboard controls: arrow keys, Home/End, Enter/Space, Escape, Tab.

- [In Modal](https://storybook-tangible-ui.pages.dev/?path=/story/forms-select--in-modal): Verifies Escape closes Select before Modal, and portal renders inside .tui-interface.

- [Placement](https://storybook-tangible-ui.pages.dev/?path=/story/forms-select--placement): Dropdown placement: bottom (default) vs top.

- [Alignment](https://storybook-tangible-ui.pages.dev/?path=/story/forms-select--alignment): Content alignment relative to trigger: start, center, end.

- [Form Example](https://storybook-tangible-ui.pages.dev/?path=/story/forms-select--form-example): Multi-field form using Field + Field.Label + Field.Control with grouped options.

- [Static (Inspect)](https://storybook-tangible-ui.pages.dev/?path=/story/forms-select--static): Static markup for devtools inspection — not interactive. Shows trigger and content states.

- [Sizes](https://storybook-tangible-ui.pages.dev/?path=/story/forms-select--sizes): Side-by-side comparison of sm, md, and lg size variants.

- [Clearable](https://storybook-tangible-ui.pages.dev/?path=/story/forms-select--clearable): Demonstrates the clear button and Delete keyboard shortcut for clearing selection.

- [With Field](https://storybook-tangible-ui.pages.dev/?path=/story/forms-select--with-field): Select inside Field with Field.Label and Field.Control for proper label association.

- [With Field Error](https://storybook-tangible-ui.pages.dev/?path=/story/forms-select--with-field-error): Select inside Field with Field.Error — demonstrates aria-invalid state and error message.

- [With Field Required](https://storybook-tangible-ui.pages.dev/?path=/story/forms-select--with-field-required): Required variant — `Field.Control` injects `aria-required` onto the trigger.

- [asChild Trigger](https://storybook-tangible-ui.pages.dev/?path=/story/forms-select--as-child): Custom trigger via asChild — merges Select props onto the child element instead of rendering a default button.

Other stories: Basic, Placeholder, Default Value, Disabled, Disabled Options, Groups, Long List, With Icons ([interactive page](https://storybook-tangible-ui.pages.dev/?path=/docs/forms-select--docs)).

## Component API tokens

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

- `--tui-select-min-width`
- `--tui-select-trigger-bg`
- `--tui-select-trigger-fg`
- `--tui-select-trigger-border`
- `--tui-select-trigger-border-hover`
- `--tui-select-trigger-border-focus`
- `--tui-select-trigger-radius`
- `--tui-select-content-bg`
- `--tui-select-content-fg`
- `--tui-select-content-border`
- `--tui-select-content-shadow`
- `--tui-select-content-radius`
- `--tui-select-option-radius`
- `--tui-select-option-bg-hover`
- `--tui-select-option-bg-selected`
- `--tui-select-option-fg-hover`
- `--tui-select-option-fg-selected`
- `--tui-select-option-bg-selected-hover`
- `--tui-select-option-check`
- `--tui-select-option-gap`

---

Interactive version: https://storybook-tangible-ui.pages.dev/?path=/docs/forms-select--docs
