Combobox
Searchable select with filtering, keyboard nav and an async-friendly API.
Built on Icons. Installs @floating-ui/react-dom and motion.
They get a notification and the issue lands in their queue.
Installation
npx shadcn@latest add https://neonkit.heyaadi.com/r/combobox.jsonUsage
import { Combobox } from "@/components/ui/combobox"
<Combobox
label="Framework"
placeholder="Search frameworks"
options={[
{ value: "nextjs", label: "Next.js", group: "Full-stack" },
{ value: "astro", label: "Astro", group: "Static" },
]}
/>Examples
Groups
Options with a group are listed under its heading. Disabled options stay visible but are skipped.
Creatable
With creatable, text that matches nothing offers a Create option. onCreate adds it and returns its value.
Server search
Pass onSearchChange to filter on the server. While loading is set, a scan line runs along the field.
Sizes, error and disabled
API reference
Combobox
Also accepts native input attributes, and the ref points at the input. className and style go to the field box.
| Prop | Type | Default |
|---|---|---|
options | ComboboxOption[]The choices, in order. Groups keep the order they first appear in. | None |
multiple | booleanPick several values, shown as chips inside the field. | false |
value | stringControlled value. An array when multiple. | None |
defaultValue | stringInitial value when uncontrolled. | None |
onValueChange | (value: string) => voidCalled with the new value. Single mode passes "" when cleared. | None |
placeholder | stringShown while the field is empty. | None |
emptyText | ReactNodeShown in the list when nothing matches. | "No matches" |
onSearchChange | (query: string) => voidCalled with the typed text. Setting it turns local filtering off, for results filtered on a server. | None |
shouldFilter | booleanFilter and rank options locally: start of label, then start of a word, then anywhere, then description. | true (false with onSearchChange) |
loading | booleanRuns the scan line and shows a loading row while there are no options. | false |
creatable | booleanOffers to create text that matches no option label. | false |
onCreate | (query: string) => stringCalled on Create. Add the option to options yourself; return its value to select it (defaults to the text). | None |
name | stringSubmits the value with a form. Multiple adds one entry per value. A form reset puts back the starting selection. | None |
required | booleanSatisfied by a picked option, not by typed text, so chips count and a half-typed search doesn't. | false |
label | ReactNodeVisible label. Names both the input and the list. | None |
description | ReactNodeHelper text under the field. | None |
error | ReactNodeRed invalid state and message. Replaces description. Pass true for the state without a message. | None |
disabled | booleanBlocks typing and opening, and leaves the value out of form data. | false |
color | NeonColorPalette name or hex. Falls back to --neon-accent, then cyan. | inherited |
size | "sm"Field height and option padding. | "md" |
containerClassName | stringClasses for the outer block with the label, field and message. | None |
ComboboxOption
| Prop | Type | Default |
|---|---|---|
value | stringUnique value, also the React key. | None |
label | stringShown in the list, the input and chips. Searched first. | None |
description | stringSecond line in the list. Also searched. | None |
icon | ReactNodeAn icon or avatar before the label, also shown in the field for the selected option. | None |
group | stringHeading to list the option under. | None |
disabled | booleanVisible but can't be picked. | false |
Accessibility
- When the browser rejects the field on submit (required, type, pattern), it turns red until it's edited or the form resets. The browser shows its own message.
- Follows the WAI-ARIA editable combobox pattern with list autocomplete: focus stays in the input and aria-activedescendant tracks the highlighted option.
- ArrowDown and ArrowUp open the list and move through it, wrapping and skipping disabled options. Home and End jump to the ends while it's open.
- Enter picks the highlighted option, Escape closes the list and restores the selected label, and Tab closes it. Space types a space.
- Typing highlights the best match, so Enter picks it. Leaving the field with an exact label typed selects that option; leaving it empty clears the value.
- In multiple mode the list stays open after each pick, Enter on a selected option removes it, and Backspace in an empty input removes the last chip.
- Result counts and added or removed values are announced through a polite live region.
- The list is portaled to the body (or the enclosing modal dialog) and sized to the field, so overflow-hidden ancestors don't clip it.
- Without a visible label, pass aria-label. It names both the input and the list.