# MultiSelect

```tsx
import { MultiSelect, useMultiSelect } from '@tangible/ui';
```

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

## MultiSelect props

| Prop | Type | Default | Description |
| - | - | - | - |
| `children` | `React.ReactNode` | | |
| `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. |
| `defaultOpen?` | `boolean` | `false` | Default open state for uncontrolled usage. |
| `defaultValue?` | `MultiSelectValue` | | Default values for uncontrolled usage. |
| `disabled?` | `boolean` | `false` | Whether the entire select is disabled. |
| `display?` | `"chips" \| "count"` | `'count'` | Display mode for the trigger. 'count': Shows "N selected"; 'chips': Shows visual chips for selected items |
| `id?` | `string` | | Optional trigger ID override. Useful for Field integration via htmlFor. |
| `labels?` | `MultiSelectLabels` | | Override internal display and screen reader strings for i18n. All keys are optional — omitted keys use English defaults. |
| `max?` | `number` | | Maximum number of selections allowed. When reached, unselected options become disabled. |
| `maxChips?` | `number` | `3` | Maximum number of chips to show before "+N more" badge. Only applies when display='chips'. |
| `onMaxReached?` | `() => void` | | Callback when max selections is reached. |
| `onOpenChange?` | `(open: boolean) => void` | | Callback when open state changes. |
| `onValueChange?` | `(value: MultiSelectValue) => void` | | Callback when selection changes. |
| `open?` | `boolean` | | Controlled open state. |
| `placeholder?` | `string` | `''` | Placeholder text when no values are selected. |
| `size?` | `"lg" \| "md" \| "sm"` | `'md'` | Control size. |
| `value?` | `MultiSelectValue` | | Controlled selected values. |

### MultiSelect.Trigger

Also exported as `MultiSelectTrigger`.

| Prop | Type | Default | Description |
| - | - | - | - |
| `asChild?` | `boolean` | `false` | When true, merges props onto the child element instead of wrapping. Child must be a single React element that accepts ref and event handlers. |
| `children?` | `React.ReactNode` | | |
| `className?` | `string` | | Additional CSS class names. |

### MultiSelect.Content

Also exported as `MultiSelectContent`.

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

### MultiSelect.Option

Also exported as `MultiSelectOption`.

| 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 and chip labels. Required when children is not a string. If children is a string, defaults to that. |

### MultiSelect.Group

Also exported as `MultiSelectGroup`.

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

### MultiSelect.Label

Also exported as `MultiSelectLabel`.

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

## Hooks

### useMultiSelect

```ts
useMultiSelect(): MultiSelectActionsContextValue & MultiSelectStateContextValue
```

Combined hook for components that need both.
Convenience for Trigger/Content that need everything.

Returns:

| Member | Type | Description |
| - | - | - |
| `disabled` | `boolean` | |
| `placeholder` | `string` | |
| `display` | `"chips" \| "count"` | |
| `maxChips` | `number` | |
| `max` | `number \| undefined` | |
| `size` | `"lg" \| "md" \| "sm"` | |
| `labels` | `Required<MultiSelectLabels>` | |
| `triggerId` | `string` | |
| `listboxId` | `string` | |
| `ariaLabel?` | `string` | |
| `ariaLabelledBy?` | `string` | |
| `ariaDescribedBy?` | `string` | |
| `ariaInvalid?` | `"false" \| "true" \| boolean` | |
| `ariaRequired?` | `"false" \| "true" \| boolean` | |
| `setOpen` | `(open: boolean) => void` | |
| `toggleOption` | `(optionValue: OptionValue, textValue: string) => void` | |
| `clearAll` | `() => void` | |
| `registerOption` | `(option: RegisteredOption) => void` | |
| `unregisterOption` | `(value: OptionValue) => 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)[]>` | |
| `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` | `MultiSelectValue` | |
| `maxReached` | `boolean` | |
| `isSelected` | `(optionValue: OptionValue) => boolean` | |
| `getSelectedOptions` | `() => RegisteredOption[]` | |
| `activeIndex` | `number \| null` | |
| `highlightedValue` | `OptionValue \| null` | |
| `orderedOptions` | `RegisteredOption[]` | |
| `floatingStyles` | `CSSProperties` | |
| `floatingContext` | `FloatingContext` | |

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

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

## Examples

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

Other stories: Basic, Chips Display Mode, Chips with Overflow, Default Value, Numeric Values, Max Selections, Disabled, Disabled Options, Groups, With Icons, Controlled, In Modal, Keyboard Navigation, Long List, Form Example, Sizes, Controlled Open, Static (Inspect) ([interactive page](https://storybook-tangible-ui.pages.dev/?path=/docs/forms-multiselect--docs)).

## Component API tokens

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

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

---

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