useCombobox@astryxdesign/core v0.7.2 · useCombobox

Usage

Tracks the active option of a single-select combobox and handles its keys, while DOM focus stays on the control. It powers Selector and CommandPalette; use it to build a combobox those components do not cover. It renders nothing and sets no attributes: the caller renders the control, the listbox and the options, and wires role, aria-expanded, aria-controls and aria-activedescendant from what it returns.

ts
import {useCombobox} from '@astryxdesign/core/Selector'

Best practices

GuidancePractices
Do

Render each option with id={getItemId(index)} and set the control's aria-activedescendant only while isOpen and highlightedIndex points at a rendered option, as CommandPalette does.

Do

For type-to-select without a search field, run useTypeahead's handler first and fall through to this onKeyDown when it returns false, as Selector does.

Do

Announce the number of results through useAnnounce when the options change for a new query.

Don't

Move DOM focus onto the options; the active option is announced through aria-activedescendant while focus stays on the control.

Parameters

ParamTypeDescription
optionsrequired
UseComboboxOptions

Configuration object.

options.selectableItemsrequired
[]

The options in the same order as they render. Options with disabled cannot be picked and are skipped while moving through the open list; ArrowDown or ArrowUp that opens the list still lands on the first or last option.

options.isOpenrequired
boolean

Whether the list is shown. The caller owns this state.

options.onOpenrequired
() => unknown

Called to show the list. Return false to refuse; the hook then leaves the highlight alone.

options.onCloserequired
() => void

Called when the hook hides the list; the hook resets the highlight first. When the caller closes the list itself (an outside press, say), it should also call setHighlightedIndex(-1).

options.listboxIdrequired
string

The listbox element's id. Option ids are derived from it through getItemId.

options.value
string

The selected value. Without search, opening by a press, Enter or Space highlights it, or the first option when nothing is selected; ArrowDown and ArrowUp open on the first and last option.

options.onSelect
(value: string) => void

Called with the picked value. Picking also closes the list.

options.onClear
() => void

Clears the value. When provided, Delete or Backspace on the closed control clears it, so clearing is not pointer-only. Ignored while the list is open or with hasSearch.

options.hasSearch
boolean (default: false)

Whether the list has its own search field. Space then types instead of picking, opening by a press or Enter does not pre-highlight (ArrowDown and ArrowUp still open on the first and last option), and printable keys on the closed control go to onSearchSeed.

options.onSearchSeed
(char: string) => void

With hasSearch, receives each printable character except Space typed while the control has focus, opening the list first when it is closed, so type-to-find needs no separate open step.

options.isDisabled
boolean (default: false)

Whether the control ignores clicks and keys.

Returns

FieldTypeDescription
highlightedIndexnumber

Index of the active option, or -1 when none is active. With hasSearch, opening by a press or Enter does not set one, so it stays -1 after the hook itself closes the list; ArrowDown and ArrowUp still open on the first or last option. Render it as the active option and name it in aria-activedescendant only while it points at a rendered option.

setHighlightedIndex(index: number) => void

Sets the active option, for example from a filter that changes the options.

getItemId(index: number) => string

The id to render on the option at index. Set the control's aria-activedescendant to isOpen && highlightedIndex >= 0 && highlightedIndex < selectableItems.length ? getItemId(highlightedIndex) : undefined, so it never names an option that does not exist, including in an empty list.

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 option; Enter (and Space without search) picks it or opens the list; Escape and Tab close it; Home, End, PageUp and PageDown jump to the first or last option. An editable control that keeps Home and End for the caret does not pass them to this handler, as CommandPalette does.

onItemSelect(item:
) => void

Picks an option from a press on it, then closes the list.

onItemMouseEnter(item:
, index: number) => void

Makes the hovered option active without scrolling the list or moving focus.

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. How compatibility works.

This install URL expires with the draft preview.
bash
npx shadcn@4.19.0 add https://astryx-jb5599s86-fbopensource.vercel.app/shadcn/hooks/use-combobox.json