Usage
Tracks the active option of a multi-select combobox and handles its keys, while DOM focus stays on the control. It powers MultiSelector; unlike useCombobox, toggling an option keeps the list open, and rows can carry an action cell. It renders nothing and sets no attributes: the caller renders the control and the list, and wires the ARIA attributes from what it returns.
tsimport {useMultiCombobox} from '@astryxdesign/core/MultiSelector'
Best practices
| Guidance | Practices |
|---|---|
| Do | Render each option with |
| Do | Use |
| Do | Announce the number of results through useAnnounce when the options change for a new query. |
| Don't | Close the list after each toggle; a multi-select stays open so several options can be picked in a row. |
Parameters
| Param | Type | Description |
|---|---|---|
optionsrequired | UseMultiComboboxOptions | Configuration object. |
options.selectableItemsrequired | [] | The options in the same order as they render. Options with |
options.isOpenrequired | boolean | Whether the list is shown. The caller owns this state. |
options.onOpenrequired | () => unknown | Called to show the list. Return |
options.onCloserequired | () => void | Called to hide the list. The highlight resets first. |
options.onTogglerequired | (itemValue: string) => void | Called with the value of the option to add or remove. Toggling keeps the list open. |
options.listboxIdrequired | string | The listbox (or grid) element's id. Option and action-cell ids are derived from it. |
options.hasSearch | boolean (default: false) | Whether the list has its own search field. Space then types instead of toggling, opening by a press or Enter does not pre-highlight (ArrowDown and ArrowUp still open on the first and last option), Delete and Backspace edit the query, and type-to-select is off. Home and End still jump to the first and last option, so a search input that keeps them for the caret should stop them before this handler and rely on PageUp and PageDown. |
options.onClear | () => void | Clears every selected value. When provided, Delete or Backspace on the closed control clears them, so clearing is not pointer-only. Ignored while the list is open or with |
options.hasValue | boolean (default: false) | Whether anything is selected. The Delete and Backspace clear is skipped when false. |
options.isGrid | boolean (default: false) | Whether each row pairs an option cell with an action cell. The inline-end arrow then moves the highlight to the row's action and the inline-start arrow back, following visual direction in right-to-left layouts, and Enter on the action runs |
options.rowHasAction | (index: number) => boolean | In a grid, whether the row at |
options.onActivateAction | (index: number) => void | In a grid, runs the control in the highlighted row's action cell. The selection does not change. |
options.isDisabled | boolean (default: false) | Whether the control ignores clicks and keys. |
Returns
| Field | Type | Description |
|---|---|---|
| highlightedIndex | number | Index of the active row, or -1 when none is active. |
| setHighlightedIndex | (index: number) => void | Sets the active row, for example after filtering. The highlight lands on the row's option cell. |
| highlightedCell | 'option' | 'action' | Which cell of the active row is highlighted; |
| activeDescendantId | string | undefined | The id to pass as the control's |
| getItemId | (index: number) => string | The id to render on the option at |
| getActionCellId | (index: number) => string | In a grid, the id to render on the action cell of row |
| onTriggerClick | () => void | Toggles the list from a press on the control. |
| onKeyDown | (e: React.KeyboardEvent) => void | Key handler for the control that keeps focus. ArrowDown and ArrowUp open the list or move the active row; Enter (and Space without search) toggles the active option and keeps the list open; Escape and Tab close it; Home, End, PageUp and PageDown jump to the first or last option; without search, typed characters move to the next option whose label starts with them, as in Selector (a repeated letter cycles, and a single letter searches from the option after the active one), and Space still toggles. |
| onItemMouseEnter | (item: , index: number, cell?: 'option' | 'action') => void | Makes the hovered option, or its action cell, active without scrolling the list or moving focus. |
Examples
Common configurations, variations, and states.A Labels field built on useMultiCombobox: typing filters the labels, the arrow keys move the active option while focus stays in the field, Enter or a click toggles a label and the list stays open, and selected labels show as removable tokens above the field.
Use with shadcn
Already using the shadcn registry workflow? Install the real Astryx package and a local public re-export. Component implementation source stays in Astryx.
This install URL expires with the draft preview.bashnpx shadcn@4.19.0 add https://astryx-6apkj4yuf-fbopensource.vercel.app/shadcn/hooks/use-multi-combobox.json