Combobox
A searchable select input with autocomplete functionality.
Installation
npx shadcn@latest add @redpanda/comboboxpnpm dlx shadcn@latest add @redpanda/comboboxyarn dlx shadcn@latest add @redpanda/comboboxbunx shadcn@latest add @redpanda/comboboxnubx shadcn@latest add @redpanda/comboboxaube dlx shadcn@latest add @redpanda/comboboxWhen to use
Use this decision tree to determine when to use the Combobox component:
Usage
import { Combobox } from '@/components/ui/combobox';'use client';
import React from 'react';import { Combobox } from '@/components/ui/combobox';
const frameworks = [ { value: 'next.js', label: 'Next.js', }, { value: 'sveltekit', label: 'SvelteKit', }, { value: 'nuxt.js', label: 'Nuxt.js', }, { value: 'remix', label: 'Remix', }, { value: 'astro', label: 'Astro', },];
export function ComboboxDemo() { const [value, setValue] = React.useState('');
return <Combobox options={frameworks} value={value} onChange={setValue} placeholder="Select framework..." />;}Behavior
Combobox is built on Base UI Combobox. The input is an ARIA
combobox: focus stays in the input while aria-activedescendant points at the highlighted option, so screen readers
announce each option as you move through the list.
| Key | Action |
|---|---|
ArrowDown / ArrowUp |
Open the list, then move the highlight. The highlight wraps around the list. |
Enter |
Pick the highlighted option. With creatable, commit the typed text when nothing is highlighted, even with the list closed. Never submits a surrounding form while the list is open. |
Escape |
Close the list. With the list closed, clear the value. |
ArrowRight |
With the caret at the end of the text, pick the highlighted option. |
Home / End |
Move the caret to the start or end of the text. |
- Typing filters options by label and value, and highlights the first match.
- Opening the list highlights the selected option.
Enterkeeps it; clicking it again clears the value. - Deleting all text clears the value.
- Closing the list without a pick restores the selected label. With
creatable, text typed for a new option stays in the input while it has focus, soEntercan still commit it; leaving the field restores the selected label. - Disabled options can be highlighted and are announced as disabled, but can’t be picked.
- While the list is open, the rest of the page is hidden from assistive technology.
Props
Combobox
optionsComboboxOption[]
Array of options to display in the dropdown.
ComboboxOption[]value?string
The currently selected value.
stringonChange(value: string) => void
Callback when the selected value changes.
(value: string) => voidplaceholder?string
Placeholder text when no value is selected.
stringdisabled?boolean
Whether the combobox is disabled.
booleanclearable?boolean
Shows a clear (X) button while a value is selected.
booleantruecreatable?boolean
If true, offers a "Create" option at the end of the list when the typed text matches no existing option.
booleanonCreateOption?(value: string) => void
Callback when a new option is created via the create action. Receives the trimmed text, which is also passed to onChange.
(value: string) => voidonInputValueChange?(value: string) => void
Called when the user edits the input text. Drive async search from here; it does not fire when picking an option fills in its label.
(value: string) => voidisLoading?boolean
Shows an inline loading status and suppresses the empty state while async options load.
booleanemptyState?React.ReactNode
Replaces the "No options found." message. Ignored while isLoading.
React.ReactNoderenderOption?(option: ComboboxOption) => React.ReactNode
Renders the content of each option row. The selected check icon is still rendered by the component.
(option: ComboboxOption) => React.ReactNodecreateLabel?string
Noun used in the create prompt (e.g. "tag", "context"). Displays as "Type to create a new {createLabel}..." when empty.
string"option"start?React.ReactNode | null
Content for the start slot of the input. Defaults to a search icon. Pass null to hide.
React.ReactNode | null<Search />className?string
Additional CSS classes for the input container.
stringonOpen?() => void
Callback when the dropdown opens.
() => voidonClose?() => void
Callback when the dropdown closes.
() => voiddefaultOpen?boolean
Renders the list open on mount, focusing the input unless preventAutoFocusOnOpen is set.
booleanpreventAutoFocusOnOpen?boolean
Keeps a defaultOpen combobox from focusing its input on mount.
booleancontainer?HTMLElement
Element to portal the list into. Defaults to the nearest portal container, then the document body.
HTMLElementtestId?string
Set as data-testid on the dropdown popup.
stringinputTestId?string
Set as data-testid on the input.
stringid?string
Set on the inner input, so a sibling <label htmlFor> both associates with it and names it.
stringaria-label?string
Names the control when no visible label points at it. Set on the inner input.
stringaria-labelledby?string
Ids of the elements that name the control, for a label the component does not render.
stringComboboxOption
valuestring
Value passed to onChange when the option is picked.
stringlabelstring
Text shown in the list and in the input; used for filtering with value.
stringgroup?string
Section heading. Options sharing a group are listed together, in first-seen order.
stringdisabled?boolean
Renders the option but blocks picking it.
booleandata?unknown
Arbitrary payload handed back to renderOption.
unknowntestId?string
Set as data-testid on the option.
stringgroupTestId?string
Set as data-testid on the option group; the first option of a group sets it.
stringLabeling
The trigger is an input with role="combobox", so it needs an accessible name of its own — a
placeholder is not one. Give it an id and point a Label at it, or pass aria-label /
aria-labelledby when the name lives in an element the component does not render.
<Label htmlFor="framework">Framework</Label><Combobox id="framework" onChange={setValue} options={frameworks} value={value} />Examples
Basic Combobox
Form Integration
Creatable Options
Disabled State
Grouped Combobox
Timezone Picker
Async Remote Search
Pair onInputValueChange with a debounced fetch and feed the resulting options back in. Toggle isLoading while the
request is in flight to show an inline spinner and suppress the default “No options found.” message. Results are still
filtered by the typed text, the first match is highlighted as soon as results arrive, and the selected option keeps its
label even when a later search no longer returns it.
<Combobox isLoading={loading} onChange={setValue} onInputValueChange={setQuery} options={options} emptyState="No topics match that query." value={value}/>Rich Option Rows
Use renderOption to render each row as a two-line label + metadata cell. Attach arbitrary per-option data via the
optional data field.
<Combobox options={BROKERS} renderOption={(option) => { const meta = option.data as BrokerMeta | undefined; return ( <div className="flex flex-col"> <span className="font-medium text-sm">{option.label}</span> {meta ? ( <span className="text-subtle text-xs"> {meta.host} · {meta.dc} </span> ) : null} </div> ); }} onChange={setValue} value={value}/>Disabled Items
Mark a ComboboxOption as disabled: true to render it as a non-selectable row. Compose with renderOption to
surface the reason it’s unavailable.
const OPTIONS: ComboboxOption[] = [ { value: 'reader', label: 'Reader' }, { value: 'maintainer', label: 'Maintainer', disabled: true }, { value: 'admin', label: 'Admin', disabled: true },];Credits
- Built on Base UI Combobox.
- We take our inspiration from Shadcn UI for the combobox component and style.