Skip to content

Combobox

Stable

A text input with a filterable list, for choosing from more options than a select can comfortably show.

Installation

$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/combobox.json

Usage

import {
  Combobox,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
} from "@/components/ui/combobox"
const frameworks = ["Next.js", "Remix", "Astro", "SvelteKit"]

<Combobox items={frameworks}>
  <ComboboxInput placeholder="Search frameworks…" />
  <ComboboxContent>
    <ComboboxEmpty>No frameworks found.</ComboboxEmpty>
    <ComboboxList>
      {(item) => (
        <ComboboxItem key={item} value={item}>
          {item}
        </ComboboxItem>
      )}
    </ComboboxList>
  </ComboboxContent>
</Combobox>

Give the root the full items array and render the list with a function: Base UI filters the items as people type and calls the function only for the matches. ComboboxEmpty appears automatically when nothing matches.

Examples

Clearable

showClear swaps the chevron for a clear button once there's a value. Offer it whenever an empty value is valid — an unassigned task, no filter — so people don't have to select the text and delete it.

Groups

Groups are just items with an items array of their own. Render them with ComboboxGroup, a ComboboxLabel heading and a ComboboxCollection for the members; filtering keeps a group only while it has matches. An InputGroupAddon passed as a child adds a leading icon.

Multiple selection

Selected values become chips inside the field. useComboboxAnchor returns a ref for the chips container so the list lines up with the whole field rather than the text input, and autoHighlight makes Enter pick the first match while typing.

Rich options

Options can show anything that helps people recognize the right one — an avatar and an email disambiguate two people with the same name. With object values, itemToStringLabel decides what fills the input and itemToStringValue what a form submits.

Button trigger

When the field should look like a button — a repository switcher, a toolbar filter — render ComboboxTrigger as a Button and move the input into the popup with showTrigger={false}.

For data that lives on a server, set filter={null} so Base UI stops filtering locally, fetch on onInputValueChange, and pass the results back in as items. Debounce keystrokes, ignore stale responses, and show that a search is in flight — here the search icon becomes a spinner.

Guidelines

When to use

  • For long lists — countries, people, time zones, repositories — where typing a few letters is faster than scrolling.
  • When people usually know what they're looking for.
  • To pick several values from a long list: labels, recipients, tags.

When not to use

  • For fewer than about fifteen options people browse rather than search — use a Select.
  • For free text with optional suggestions, such as a search box — use an Input with your own suggestion list; a combobox commits to one of its items.
  • To run commands — use a Command palette.

Matching

Match anywhere in the label, not only at the start (york should find New York), and ignore case and accents (krakow should find Kraków — pass a locale when your data isn't English). Put the most likely matches first, and keep the empty state specific: say what wasn't found and, when you can, what to do instead.

No teammates match “Grce”Check the spelling or invite them by email.
Do.A specific empty state that names the query and suggests a next step.
No data
Don’t.A bare message leaves people guessing whether the list is broken.

Accessibility

The input has role="combobox", aria-expanded and aria-autocomplete="list", and controls a listbox popup. Focus stays in the input while the highlight moves through the options, following the ARIA combobox pattern.

KeyBehavior
A–Z
Typing filters the list and opens it.
Opens the list and moves the highlight. At either end, the highlight returns to the input.
Enter
Chooses the highlighted option.
Esc
Closes the list; the input keeps its text.
Backspace
In a multiple combobox with an empty input, removes the last chip.
Tab
Moves focus to the next control and closes the list.
  • Label the input. Point a visible FieldLabel at the input's id, or use aria-label in toolbars.
  • Announce loading. When results come from a server, give the spinner an accessible label (the Spinner component defaults to Loading) and keep the empty state's text meaningful.
  • Chips. Each chip's remove button is named after the chip — "Remove Design" rather than just "button" — in the language set by LocaleProvider. Pass removeLabel when the chip's content isn't plain text. Remove buttons stay out of the tab order; keyboard users remove chips with Backspace.
  • Toggle button. The chevron is labelled "Toggle options" and is skipped by Tab — it's a pointer affordance; the input already opens the list from the keyboard.
  • Boundaries and targets. The field uses the input token for its border (3:1, WCAG 1.4.11); options are at least 32px tall.

API reference

Combobox

The root. Doesn't render an element. Accepts every prop of Base UI's Combobox.Root.

PropTypeDefault
items

Every option, or groups of options. Filtered as people type.

any[] | Group[]No default
value / defaultValue

The selected value, controlled or initial.

Value | Value[] | nullNo default
onValueChange

Called when the selection changes.

(value, details) => voidNo default
multiple

Allows several selections; render them as chips.

booleanfalse
inputValue / defaultInputValue

The text in the input, controlled or initial.

stringNo default
onInputValueChange

Called on every keystroke — the place to fetch remote results.

(value, details) => voidNo default
filter

Custom matching. Pass null to disable local filtering for server-side search.

((item, query) => boolean) | nullNo default
filteredItems

Externally filtered items, when you control filtering yourself.

any[]No default
autoHighlight

Highlights the first match while typing, so Enter selects it.

boolean | "always"false
openOnInputClick

Opens the list when the input is clicked.

booleantrue
limit

Caps how many matches render. -1 means no limit.

number-1
itemToStringLabel / itemToStringValue

Converts object values to the input text and the submitted value.

(value) => stringNo default
locale

Locale for accent- and case-insensitive matching.

Intl.LocalesArgumentNo default
name / required / disabled / readOnly

Form and interaction props, as on native inputs.

string / booleanNo default
modal

Locks page scroll and outside interaction while open.

booleanfalse

ComboboxInput

The text field, rendered inside an Input Group. Children are added to the group, e.g. an InputGroupAddon with an icon.

PropTypeDefault
showTrigger

Shows the chevron button that toggles the list.

booleantrue
showClear

Shows a clear button when there is a value.

booleanfalse
disabled

Disables the input and its buttons.

booleanfalse
className

Applied to the input group — use it for width.

stringNo default

ComboboxContent

The positioned popup.

PropTypeDefault
anchor

Element to align with — the chips container for multiple selection.

RefObject<Element>No default
side

Preferred side of the anchor.

"top" | "bottom" | "left" | "right""bottom"
sideOffset

Gap from the anchor, in pixels.

number6
align

Alignment along the side.

"start" | "center" | "end""start"

ComboboxList, ComboboxItem, ComboboxEmpty

PropTypeDefault
ComboboxList children

Renders each filtered item or group.

ReactNode | (item, index) => ReactNodeNo default
ComboboxItem valuerequired

The value the option represents.

anyNo default
ComboboxItem disabled

Prevents choosing the option.

booleanfalse
ComboboxEmpty children

Shown only when no items match.

ReactNodeNo default

Groups

ComboboxGroup takes the group's items; ComboboxLabel titles it; ComboboxCollection renders its members with a function; ComboboxSeparator divides groups.

Chips

ComboboxChips is the field for multiple selection (attach the ref from useComboboxAnchor()), ComboboxValue renders the selected values with a function, ComboboxChip shows one value with a remove button (showRemove, default true; removeLabel overrides its accessible name), and ComboboxChipsInput is the text input inside the field.

ComboboxTrigger and ComboboxValue

ComboboxTrigger opens the list from a custom element — use render to make it a Button. ComboboxValue renders the current value's label.