Skip to content

Combobox

<Combobox> is a searchable single-select. Composed from shadcn’s canonical Command (cmdk) + Popover. Reach for it whenever the option list is long enough that scrolling the Select would be slower than typing.

import from "@na/ui/components/Combobox" ▶ Open in Storybook packages/ui/src/components/Combobox.tsx

When to use

  • Pickers from a list of ~10–500 options (countries, owners, tags).
  • Async-loaded options (search-as-you-type).
  • Anywhere Select gets too long.

Don’t use Combobox for: short static lists ≤ ~10 (Select is faster), 2–5 visible-at-once choices (SegmentedControl / RadioGroup), or app-wide commands (CommandPalette — planned).

Default

Type to filter. Click an option to select. Click the same option again to clear.

<Combobox
options={[
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' },
{ value: 'vn', label: 'Vietnam' },
]}
value={value}
onValueChange={setValue}
triggerLabel="Select a country"
placeholder="Search countries…"
/>

API

Prop Type Default Description
options * ComboboxOption<TValue>[] List of options. Each: { value, label, disabled?, searchValue? }.
value TValue Controlled. Pair with onValueChange.
defaultValue TValue Initial value for uncontrolled mode.
onValueChange (value: TValue | undefined) => void undefined when the user clears the selection.
placeholder string "Search…" Search-input placeholder inside the popover.
triggerLabel ReactNode "Select…" Trigger button label when no value is selected.
emptyState ReactNode "No results." Shown when the search filter matches nothing.
ariaLabel string Required when triggerLabel is non-string and there is no surrounding Label.
disabled boolean false Disable the trigger.
matchTriggerWidth boolean true Popover content matches trigger width.
className string Forwarded to the trigger Button.
contentClassName string Forwarded to PopoverContent.

ComboboxOption<TValue>

Prop Type Default Description
value * TValue Unique identifier within the options list.
label * ReactNode Visible label.
disabled boolean false Skip during keyboard nav and click.
searchValue string Override the string used for filtering. Defaults to the label when string.

Design guidelines

✓ Do

  • Use Combobox once the option list grows past ~10. The search box earns its space.
  • Use searchValue when the visible label is rich (icon + text) — typing should match the text.
  • Pre-sort options by the user's frequency of use, then alphabetical for the long tail.

✗ Don't

  • Use Combobox for tiny static lists. Select is faster.
  • Render multi-select as a Combobox with checkboxes inside. The MultiCombobox primitive is on the roadmap.
  • Skip the empty state. "No results." prevents the popover from looking broken when filters are tight.

Accessibility

  • Trigger is role="combobox" with aria-expanded.
  • Inner Command provides full ARIA listbox semantics + keyboard.
  • Keyboard: Enter opens, / navigate, Enter on item selects, Esc closes.
  • Type-to-filter is automatic.
  • Select — When the list is short and static.
  • Command — Underlying primitive (cmdk).
  • CommandPalette — App-wide ⌘K palette (planned).

▶ Open Combobox stories in Storybook