Combobox

Finds one or more values in a long list by typing.

Examples

Pick an agent

Emails you paste in

Paste a list from anywhere.

Labels you can add to

Mode

  • singledefault

  • 2 tools allowed.

    multiple

  • creatable

  • email

Sizes

  • sm

  • defaultdefault

  • lg

States

  • rest

  • hover

  • filled

  • invalid

  • disabled

  • pending

  • open

  • No agents match.

    empty

Usage

When to use it
Use
More than about seven options, so typing beats scanning: agents, models, queues, tools.
Use
Several values from a list, shown together: tools an agent may call, labels on a conversation.
Use
Free values the list cannot know: new labels, the emails allowed to review a draft.
Not for
Seven or fewer options and no typing needed. Use Select.
Not for
Two to five options that should all stay visible. Use Segmented control.
Not for
A search across the product that runs commands. Use Command.
Not for
One line of free text with no list. Use Input.

Rules

8 rules
Must
Set multiple creatable with string items, and a validate or preset when the format matters. Because creatable stores typed entries as strings, and without validate a typo like "ana@" commits as a valid chip.
Must
Pass pending while items load and filter={null} so the server order stands. Because without pending the list says nothing matched while results load, and the client filter reorders and drops server matches.
Should
Set showClear on an optional single field; it has no empty option. Because once a value is picked there is no other way back to empty.
Should
Name what was searched in the empty text, as 'No agents match.'
Must
Pass the value name as a plain string child of ComboboxChip. Because the remove button is named from string children only, so a chip holding an element gets a button called just "Remove".
Never
Use a combobox for 7 options or fewer; use Select. Because typing to filter a list that already fits on screen is an extra step, and Select shows every option at once.
Must
Label the field with FieldLabel or aria-label. Because a placeholder is not a name and disappears on the first keystroke, so a screen reader hears "combobox".
Never
Lift the chips field height cap; it stops at four rows (176px) and scrolls. Because a pasted list of 200 emails would otherwise grow the field and push the rest of the form off the screen.

Accessibility

7 checks
The input is role="combobox" with aria-expanded, aria-controls, aria-activedescendant and aria-invalid.
The list is a listbox (multiselectable with multiple); items are options with aria-selected.
ArrowDown and ArrowUp open and move; Enter picks or adds; Escape closes, a second Escape reaches the dialog.
Backspace on an empty chips input removes the last chip; ArrowLeft at the start moves into the chips.
Adds, removes and Searching are announced in polite status regions.
A marked chip says its reason in title and sr-only text, so color is not the only cue.
The chip remove button reads "Remove" plus the value; the chevron trigger reads "Show options" and stays out of the tab order.

Edge cases

13 edge cases
Long chip
Truncates inside the field; a 36-character email fits 320px.
One-letter chip
Keeps a 24px remove button so neighbours stay separate targets.
items=[]
Not creatable: the empty text. Creatable: nothing until the person types.
24 chips
Wrap; the field caps at 176px and scrolls; the popup caps at the available height.
Right to left
Chips flow right to left; the check sits at the inline end.
Slow search
Keeps the Searching row and the old items until the query changes.
Invalid email
Kept as a marked chip; the field turns invalid and announces the count.
Pasted list
Splits into chips; a quoted `"Dee, Ops" <dee@acme.example>` stays one address.
Enter held down
Adds once; a repeat clears the text and flashes the existing chip.
Inside a dialog
Escape closes the list first, then the dialog.
Inside a form
Enter picks or adds and does not submit while the list is open.
Forced colors
The field and chips draw 1px system borders; the highlight gets an outline.
Reduced motion
The popup opens without a transition; the spinner stops.

API

9 props
Combobox items, value, defaultValue, onValueChange, multiple
Base UI
creatable
boolean default false
preset
'email'
normalize, validate, split
(raw) => string, (v, all) => string | null, (text) => string[] default trim, none, commas and lines
delimiters
string[] default [',']
pending
boolean default false
ComboboxField
items, multiple, placeholder, emptyText, size, showClear
ComboboxInput size, showSearchIcon, showTrigger, showClear
'sm' | 'default' | 'lg', boolean default 'default'
ComboboxChip value, showRemove
string, boolean default , true

Tokens

26 tokens
--line-control
oklch(var(--ink-c) / 0.16)
--line-frame
oklch(var(--ink-c) / var(--line-frame-alpha))
--accent-wash
var(--surface-selected)
--accent-mark
var(--ink)
--line-focus
2px
--focus-halo
0 0 0 3px oklch(var(--ink-c) / 0.08)
--border-focus
var(--ink)
--surface-pressed
oklch(var(--ink-c) / 0.08)
--radius-inner
4px
--radius-soft
6px
--type-label-sm
400 12px/16px var(--font-sans)
--surface-inverse
var(--ink)
--text-inverse
var(--paper)
--fail-text
oklch(51% 0.180 26)
--surface-page
var(--paper)
--elev-overlay
0 0 0 1px var(--line-frame), 0 12px 24px -12px oklch(var(--text-on-field-c) / var(--shadow-alpha))
--dur-base
160ms
--dur-fast
120ms
--ease-step
cubic-bezier(0.2, 0, 0, 1)
--surface-hover
oklch(var(--ink-c) / 0.05)
--type-label
400 14px/16px var(--font-sans)
--type-tag
var(--type-code-sm)
--text-2
oklch(var(--ink-c) / 0.80)
--field-max-height
calc(var(--control-lg) * 4)
--dur-spin
800ms
--ease-spin
linear

Install

For your coding agent

GitHub

Please star me pookie

141

A new design system every week

Leave your email before you copy the prompt.