Skip to content

Combobox

A text input that filters a list of options as people type, for picking from long lists.

Preview

Installation

pnpm add @syntara/react @syntara/tokens
import { Combobox, ComboboxItem, ComboboxSection } from '@syntara/react';

Usage

import { Combobox, ComboboxItem } from '@syntara/react';

export function CountryField() {
  return (
    <Combobox label="Country" placeholder="Search countries…" onSelectionChange={(key) => console.log(key)}>
      <ComboboxItem id="in">India</ComboboxItem>
      <ComboboxItem id="id">Indonesia</ComboboxItem>
      <ComboboxItem id="jp">Japan</ComboboxItem>
    </Combobox>
  );
}

Examples

Custom value

allowsCustomValue keeps text that isn't in the list.

Not in the list? Type your own.

Sections, icons and descriptions

Group options under headings; items can have an icon and a second line.

Invalid and disabled

Required with an error message, and a disabled combobox.

Choose the department this request belongs to.

Accessibility

KeysAction
Type charactersFilters the list and opens it.
↓or↑Opens the list; moves the highlighted option while focus stays in the input.
EnterChooses the highlighted option and closes the list.
EscapeCloses the list and puts back the text of the selected option (typed text is kept with allowsCustomValue).
TabCommits the current text or selection and moves focus on.
  • The input is role="combobox" with aria-expanded, aria-controls and aria-activedescendant; the highlighted option is announced as you arrow.
  • The chevron button is for pointer users and is excluded from the tab order.
  • The empty state is rendered inside the listbox so screen readers announce it.
  • Motion follows prefers-reduced-motion: the list still fades, but the spring entrance, the rows stepping in on open (first 8, 30ms apart; not while filtering), the press dip, the field's growing halo and the chevron flip only run when motion is allowed.

Guidelines

Do

  • Use for long lists (15+ options) people know by name: countries, people, accounts.
  • Write a placeholder that says what can be searched.
  • Use allowsCustomValue only when free text is a valid answer.

Don’t

  • Don't use for short lists — a Select is quicker to scan.
  • Don't use for search that navigates or runs actions — use SearchField or Command.
  • Don't hide the label behind the placeholder.

API reference

Combobox

label
ReactNode

Visible label. Use aria-label only when a visible label is impossible.

description
ReactNode

Help text under the input, linked with aria-describedby.

errorMessage
string | ((validation: ValidationResult) => string)

Shown under the input when the field is invalid.

placeholder
string

Placeholder text in the input.

emptyState
ReactNode

Row shown when nothing matches the typed text.

Default 'No results'

items / defaultItems
Iterable<T>

Items for a dynamic list. Use defaultItems to let the combobox filter them; items when you filter yourself.

childrenRequired
ReactNode | ((item: T) => ReactElement)

ComboboxItem and ComboboxSection elements, or a function that renders one per item.

selectedKey / defaultSelectedKey
Key | null

The selected item's id, controlled or initial.

onSelectionChange
(key: Key | null) => void

Called when an option is chosen or the selection is cleared.

inputValue / defaultInputValue
string

The text in the input, controlled or initial.

onInputChange
(value: string) => void

Called as the text changes.

allowsCustomValue
boolean

Keep typed text that doesn't match an option instead of reverting on blur.

Default false

menuTrigger
  • inputdefault
  • focus
  • manual

What opens the list: typing, focusing, or only the button and arrow keys.

defaultFilter
(textValue: string, inputValue: string) => boolean

Custom filter for defaultItems. Defaults to a locale-aware 'contains'.

allowsEmptyCollection
boolean

Keep the list open to show emptyState when nothing matches.

Default true

size
  • sm
  • mddefault
  • lg

Height of the box: md is the density's control height, sm one 8px step shorter, lg two steps taller. Height, corner radius and inline inset match a Button of the same size, so a field and a button in one row line up.

isRequired / isInvalid / isDisabled
boolean

Field states; isRequired also shows the label's required marker.

Default false

ComboboxItem

idRequired
Key

Unique key used by selectedKey and onSelectionChange.

childrenRequired
ReactNode

The option label. A string also becomes its textValue, which fills the input when chosen.

textValue
string

Plain text used for filtering and the input when children is not a string.

description
ReactNode

A second, quieter line under the label.

icon
ReactNode

Decorative leading icon.

ComboboxSection

title
ReactNode

Heading for the group; also its accessible name. Sections with no matches hide while filtering.

Tokens

The semantic tokens this component reads, grouped by what they control. Swatches show this site’s theme; change a tenant’s brand and the component follows with no code change.

Colour12
text.subtletext.defaultfocus.ringtext.disabledborder.defaultsurface.raisedborder.subtlesurface.selectedborder.strongsurface.defaultsurface.sunkensurface.canvas
Type8
font.size.mdfont.bodyline-height.normalfont.size.xsfont.weight.mediumline-height.snugfont.size.smfont.tracking.sm
Space and size7
space.2space.1control-heightspace.6space.16control-padding-inlinespace.4
Shape2
radius.containerradius.field
Depth4
glass.opacityglass.blurshadow.overlayhairline
Motion6
motion.duration.fastmotion.easingmotion.duration.springmotion.springmotion.duration.normalmotion.easing-out
Other2
sheenrim