Skip to content

Components · Combobox

Combobox

Search, then choose. Downshift owns the WAI-ARIA combobox contract — active-descendant bookkeeping, roving highlight, Home/End, typeahead, Escape and Enter. LUC owns the field, the menu, the grouped rows and the loading and no-results states. Nothing here fetches anything.

import { Combobox } from "@luc/ui";

Examples

Searchable selection

The default matcher is a case-insensitive contains across the label and the description.

Type a ticker or a name.

Selected: nothing yet

Grouped by region, filtered as you type. It starts with a chosen value, and opening it still offers every country — the field shows the selection, the menu is not filtered by it.

Disabled keeps its value readable.

An empty result is a state, not a blank popover.

Sizes and the clear affordance

One size token drives the field, the glyph padding and the row rhythm. The clear button is the default and can be turned off when the value may never be unset.

A value the form requires: the clear affordance would offer an invalid state, so it is gone and the value can only be replaced.

Async-friendly, without owning the network

Pass inputValue, onInputValueChange and loading, and hand back options when your request resolves. The component never calls fetch.

240 prepared series, filtered by the caller.

Usage

import { Combobox } from "@luc/ui";
<Combobox options={instruments} value={instrument} onValueChange={setInstrument} aria-label="Instrument" />

`options` is the list and `value` is the whole selected option rather than its id. Nothing here fetches: pass `loading` and hand back options from a request you own.

API

Read from the published TypeScript API, so a prop that is not listed here does not exist and a default that is listed is the one the compiler sees.

Exported symbols
ExportKindSignature
Combobox21 documented props({ options, value, onValueChange, inputValue, onInputValueChange, filter, placeholder, emptyMessage, loading, disabled, clearable, clearLabel, openLabel, closeLabel, size, id, name, className, maxHei…

Combobox props

Type: ComboboxProps

Values the compiler reads from the declaration.

Combobox props
PropTypeDefaultNotes
aria-label?string | undefined—Accessible name for the input. Required unless a `Field` label is present.
className?string | undefined—
clearable?boolean | undefined—
clearLabel?string | undefined—Accessible names for the three built-in buttons. Defaults are English; a product in another language passes its own strings rather than forking the component. The open/close pair …
closeLabel?string | undefined—
disabled?boolean | undefined—
emptyMessage?ReactNode—Announcement + body when nothing matches.
filter?((option: ComboboxOption, query: string) => boolean) | undefined—Replacement matcher. Default: case-insensitive `label`/`description` contains.
id?string | undefined—
inputValue?string | undefined—Query text (controlled) — the async path.
loading?boolean | undefined—
maxHeight?string | undefined—Max height of the scrollable list, e.g. `18rem`.
name?string | undefined—
onInputValueChange?((query: string) => void) | undefined—
onValueChange?((option: ComboboxOption | null) => void) | undefined—
openLabel?string | undefined—
optionsreadonly ComboboxOption[]—
placeholder?string | undefined—
renderOption?((option: ComboboxOption, state: { selected: boolean; highlighted: boolean; }) => ReactNo…—Render a distinct row (icon, avatar…). Falls back to the default row.
size?ControlSize | undefined—
value?ComboboxOption | null | undefined—Selected option (controlled).

Guidelines

Do

  • Use it when the reader has to search to find the value; a handful of options they recognise on sight is `Select`.
  • Pass `inputValue` and `onInputValueChange` when the list comes from a request, and keep that request in your own code.
  • Write `emptyMessage` as a sentence about the query — an empty result is a state the reader has to be told about.

Don't

  • Wait for the empty state to explain a failed request: `emptyMessage` says nothing matched, `loading` says work is in flight.
  • Filter a large remote list in the browser because the control feels instant; `filter` is a matcher for the options you already hold.
  • Assume the clear button can be removed for tidiness — set `clearable={false}` only when the value may never be unset.

Accessibility

The input keeps `role="combobox"` with `aria-expanded` and `aria-activedescendant`, and the list is a `role="listbox"` whose rows are `role="option"`. Arrow Down and Arrow Up move the highlight without moving focus, Enter selects what is highlighted, Escape closes without changing the value, and Tab away closes the list instead of leaving it floating. The three built-in buttons take `clearLabel`, `openLabel` and `closeLabel` so a page in another language can name them.

Luc UI v1.0Comboboxdark theme