MultiSelect
Searchable multi-value select with a popover listbox, chips and grouping
Category: inputs · WCAG 2.2-AA · form, select, multi, input, popover, combobox, tags
Sizes
- sm
- md
- lg
States
- closed
- open
- error
Props
| Prop | Type | Default | Description |
|---|---|---|---|
options | MultiSelectOption[] | — | The selectable options. |
value | string[] | — | The controlled value. Omit it and pass defaultValue to let the component own the selection. |
defaultValue | string[] | [] | The initial value when uncontrolled. |
onValueChange | (value: string[]) => void | — | Called with the new value when it changes. |
placeholder | string | — | Placeholder text shown when the field is empty. |
label | string | — | Visible field label, rendered above the trigger. |
ariaLabel | string | — | Invisible accessible name, for when a visible element outside this component already labels it and `label` would render that text a second time. ⚠ `label` on this component is **visible**; `IconButton.label`/`Sparkline.label` are invisible names, which is the prior that costs adopters a duplicated label. |
aria-labelledby | string | — | Wired automatically by a wrapping `Field` — its label id, forwarded to the focusable control so the Field's label names it. |
aria-describedby | string | — | Wired automatically by a wrapping `Field` — the ids of its hint/error text, forwarded to the focusable control so the supporting text is announced. |
aria-invalid | boolean | — | Wired automatically by a wrapping `Field` when it is in an error state. |
hint | string | — | Helper text below the field. |
error | string | — | Error text below the field; also marks the control invalid. |
display | 'count' | 'chips' | 'count' | How the trigger summarises the selection: a count, or one removable chip per value. |
disabled | boolean | false | When true, disables the control and removes it from the tab order. |
clearable | boolean | false | When true, shows a control that clears every selected value. |
selectAll | boolean | false | When true, shows a row that selects or clears every enabled option at once. |
max | number | — | Maximum number of values that may be selected. Further options become unselectable once reached. |
creatable | boolean | false | When true, offers the current search text as a new option. |
onCreate | (label: string) => void | — | Called with the typed label when the user picks the "create" row. |
loading | boolean | false | When true, the list reports itself as busy and shows a loading row instead of the no-results message. |
onSearchChange | (query: string) => void | — | Called with the search text on every keystroke. Pair it with filter={() => true} for a server-driven list. |
filter | (option: MultiSelectOption, query: string) => boolean | — | Replaces the built-in diacritic-insensitive matcher. |
searchable | boolean | true | When true, shows the search field. The list is keyboard-navigable either way. |
size | 'sm' | 'md' | 'lg' | 'md' | Field height. |
name | string | — | Submitted with a surrounding form — one hidden input per selected value. |
labels | MultiSelectLabels | — | Overrides for the component’s user-visible strings (i18n). |
id | string | — | Id for the trigger control. |
Design tokens
--cascivo-color-surface--cascivo-color-bg-subtle--cascivo-color-border--cascivo-color-border-strong--cascivo-color-text--cascivo-color-text-muted--cascivo-color-accent--cascivo-color-destructive--cascivo-font-medium--cascivo-font-bold--cascivo-radius-field--cascivo-radius-overlay--cascivo-radius-item--cascivo-radius-indicator--cascivo-shadow-md--cascivo-focus-ring--cascivo-motion-enter--cascivo-target-min-coarse
When to use
- Selecting several values at once from a known list of options
- Lists long enough that the built-in search/filter helps the user find options
- Cases needing a compact trigger that summarizes the selected count, or chips per value
- Server-driven option lists, via onSearchChange plus filter={() => true}
When not to use
- Choosing exactly one value — use Select
- Free-text entries with no option list behind them — use TagsInput
- A handful of always-visible options — use a Checkbox group
How to build an accessible MultiSelect in React →
Examples
Basic
<MultiSelect options={[{label:'One',value:'1'},{label:'Two',value:'2'}]} defaultValue={[]} />Chips with a clear control
Each selection renders as a chip with its own remove button.
<MultiSelect options={options} display="chips" clearable defaultValue={['1']} />Grouped options
Options carrying a group render under a labelled role="group" heading.
<MultiSelect options={[{label:'Apple',value:'a',group:'Pome'},{label:'Cherry',value:'c',group:'Stone'}]} />Remote search
filter={() => true} hands filtering to the server; loading marks the list busy between keystroke and response.
<MultiSelect options={results} loading={pending} onSearchChange={search} filter={() => true} />Bounded selection
Options past the limit report aria-disabled; select-all stops at the limit.
<MultiSelect options={options} max={3} selectAll />