Skip to content

Combobox

Open .md
Live preview

Loading example…

Searchable single select (WAI-ARIA combobox with a listbox popup). The input shows the typed text while editing and the selected label otherwise; focus never leaves the input and the highlighted option is announced through aria-activedescendant. Works with local options or with results the caller loads from onSearchChange.

import { Combobox } from "@jcsoftdev/ui/forms";

Single-component entry points tree-shake to just this component: @jcsoftdev/ui/forms/combobox.

<Combobox
aria-label="School"
options={schools}
placeholder="Search a school"
/>
Prop Type Default Description
options * readonly (ListboxOption | ComboboxGroup)[] Options, optionally nested in labelled groups. For async search, pass the current results.
value string Selected option value (controlled). "" means nothing is selected.
defaultValue string ""
onValueChange ((value: string, option: ComboboxOption | null) => void)
onSearchChange ((query: string) => void) Called on every keystroke with the text typed, and with "" when the search resets. Debounce in the caller before fetching; the component never delays or cancels requests.
filter boolean | ((option: ComboboxOption, query: string) => boolean) true Local filtering. true (default) matches label, description and keywords ignoring case and accents; false shows options as given (server-side search); a function decides per option.
maxVisible number 50 Most options rendered at once. Default 50.
loading boolean false Shows the loading status, e.g. while an async search is in flight.
loadingText string
emptyText string Shown when nothing matches. Defaults to the localized “No results”.
clearable boolean true Shows a button that clears the selection. Default true.
clearLabel string
valueLabel string Label to show for value while it is not (yet) among options, e.g. after an async search moved on.
name string Submits the value (not the typed text) with a surrounding <form> through a hidden input.
invalid boolean
nativeRequired boolean false required always sets aria-required. The native attribute, which blocks form submission with the browser’s own bubble before your validation can render, is added only when this is true. Default false.
size "sm" | "md" "md"
container HTMLElement | null Portal target for the popup. Defaults to document.body; pass an element to keep a scoped data-style.
material "solid" | "glass" | "glass-clear" | "liquid" | "liquid-clear" Popup surface material; see PopoverContent.
classNames Partial<Record<ComboboxPart, string>> Per-part classes merged with cn; consumer classes win. className still targets the root and is merged last.

* marks a required prop.

It also accepts the native props of the element or primitive it renders (for example className, id, aria-* and event handlers).

packages/ui/src/forms/combobox/combobox.tsx