Noctis

Combobox

A filterable select. A text input narrows a floating list of options as you type, and you pick from the matches — for single or multiple selection, with an async loading story and a real validation state.

Basic

Type to filter; the popup opens as you type and Enter selects the highlighted option. The field follows the rest of the field family — a filled surface, a calm ring-less focus that shifts only the border, and a subtle rest shadow — while the option rows reuse the menu highlight idiom (a neutral fill, not the accent).

Sizes

Two control heights — medium and large — sharing the field type and spacing rhythm. Set size on Combobox.Input (or once on Combobox.Root to apply it to the field).

Trigger and clear

Add a Combobox.Trigger to open the list with a click — its chevron rotates while open — and a Combobox.Clear to reset the value, shown only while the field holds one. Wrap them with the input in a Combobox.InputGroup: the input reserves trailing room so the stacked buttons pin to the field's inline-end without ever sitting over the typed text.

Leading icon

Add a Combobox.Icon as the first child of the InputGroup for a leading adornment — a search magnifier by default — signalling that the field is a filter. It reserves a leading column and mirrors to the inline-start under RTL.

Async loading

A combobox's defining use case is autocomplete from a server. Set loading on Combobox.Root while a request is in flight — the trailing chevron becomes a spinner — and render a Combobox.Status inside the Content so a screen reader hears the loading state and the results count politely. Disable the built-in filter (filter={null}) when you filter on the server, and debounce the input. Keep already-selected items in the list so the current value stays valid across fetches.

In a form

Compose the combobox inside a Field.Root for the full label, description, and error contract. The field reports invalid through Field automatically; set invalid on Combobox.Input as an additive override. Invalid shifts the border to the danger role — the same recipe as Input, no bespoke red and no extra ring.

Who should own this issue.

Owner is already assigned. Pick someone else.

Match highlighting

Bold the matched substring with a <mark data-slot="noctis-combobox-item-match"> around it as you render each row. The mark is weight-and-foreground only — no highlight background, never the accent — so the match reads clearly while the list stays quiet.

Multiple selection

Set multiple on Combobox.Root and wrap the chips and the input in a Combobox.ChipsInput — the multi-select field shell that carries the field fill, border, and a single ring on :focus-within, so the selection reads as one field rather than floating chips. Render the selected values with Combobox.Chips, Combobox.Value, Combobox.Chip, and Combobox.ChipRemove (which brightens on hover).

bug
help wanted

Grouped options

Group related options under headings with Combobox.Group and Combobox.GroupLabel, mapping each group's filtered items through Combobox.Collection. Add a Combobox.Separator between groups for a thin neutral divider.

Structured options

An option is just composition — give a row a leading avatar or icon, a primary label, a secondary description line, and trailing meta. The leading check column is reserved by Combobox.Item regardless, so structured rows still align.

Create a new value

There is no dedicated create slot — compose it. Allow free text with allowsCustomValue on Combobox.Root, then offer a create action inside Combobox.Empty (e.g. a button that commits the current query as a new value) so the no-results state turns into Create "<query>".

Long lists

For hundreds or thousands of rows (countries, timezones), pass virtualized to Combobox.Root and render the Combobox.List with the virtualized item API from Base UI — only the visible rows mount. Keep the item height fixed so the virtualizer can measure it.

Keyboard

Filtering and navigation are Base UI's, fully keyboard-operable and RTL-aware.

KeyAction
TypeFilters the list and opens the popup
Down / UpMove the highlight between options
Home / EndJump to the first / last option
EnterSelect the highlighted option
EscapeClose the popup, keeping the field focused
BackspaceIn multiple mode with an empty field, removes the last chip

Arrow keys follow the visual list order, so they read the same under RTL; directional glyphs mirror with direction.

Accessibility

  • Async announcements. Combobox.Status is a polite live region that must stay mounted — Base UI announces its content changes (loading, results count) without moving focus. The trailing spinner also carries a localized hidden "Loading" label.
  • Affordance labels. Trigger, Clear, and ChipRemove are icon-only buttons — always give them an aria-label.
  • Known gap. Base UI's internal dismiss control has a hardcoded English aria-label ("Dismiss") that no consumer prop reaches, so it is not yet translatable from this layer — pending an upstream Base UI change. Everything else localizes through the active locale.
  • Reduced motion. The spinner, the popup scale, and the chevron rotation all respect prefers-reduced-motion.

Anatomy

Compose a combobox from its parts. Combobox.Root owns the value and filtering (controlled via value / onValueChange, or uncontrolled via defaultValue) and shares the control size and the loading flag.

  • Combobox.Root — the container. Props: size (default md), loading, multiple, plus the Base UI Combobox.Root props (items, filter, filteredItems, value, defaultValue, onValueChange, onInputValueChange, virtualized, allowsCustomValue, open, defaultOpen).
  • Combobox.Input — the text field. Type to filter; takes an optional size override and an invalid flag.
  • Combobox.InputGroup — the field shell that lays the input beside its leading Icon and trailing Trigger/Clear.
  • Combobox.ChipsInput — the multi-select field shell: a filled, bordered box wrapping the Chips + Input, ringing on :focus-within.
  • Combobox.Icon — a leading decorative field glyph (search magnifier by default).
  • Combobox.Trigger — a button that opens the popup; renders a chevron that rotates while open, or a spinner while the root is loading.
  • Combobox.Clear — a button that resets the value, shown only while the field holds one.
  • Combobox.Content — the floating, elevated listbox surface (portaled through Surface).
  • Combobox.Status — a polite live region for async messages, rendered inside Content.
  • Combobox.List — the scrollable listbox; accepts an item-render child.
  • Combobox.Item — one option, with a leading check that appears once selected.
  • Combobox.Empty — the centred message shown when the filter matches nothing.
  • Combobox.Group / Combobox.GroupLabel / Combobox.Collection / Combobox.Separator — a labelled section, its filtered rows, and the divider between sections.
  • Combobox.Chips / Combobox.Value / Combobox.Chip / Combobox.ChipRemove — the selected-value chips for multiple selection.

Every rendered part carries a data-slot (combobox-input, combobox-list, combobox-item, combobox-item-indicator, combobox-empty, combobox-group-label, combobox-chip, and the styling-only combobox-input-group, combobox-chips-input, combobox-icon, combobox-trigger, combobox-spinner, combobox-clear, combobox-positioner, combobox-content, combobox-status, combobox-group, combobox-separator, combobox-item-match, combobox-chips, combobox-chip-remove) for host-side styling — pair it with the Base UI state attributes (data-popup-open, data-highlighted, data-selected, data-disabled, data-list-empty, data-invalid).

On surfaces

The same control re-tuned across the elevation scopes — the root canvas, an elevated panel, a menu, and a sunken well. It stays legible on every layer.

root
elevated
menu
sunken

Design tokens

Generated from the component's declaration — the same graph that mints the CSS, so a variable name or its resolution default can't drift. The minted tokens are the public override seam: set one on any ancestor and every combobox in that region retunes — e.g. .brand { --noctis-combobox-item-background-color-highlighted: var(--noctis-color-accent-muted); } recolors the option highlight. Knobs that aren't minted are reached through the part's data-slot. See Customization for the full override ladder and Tokens for the whole graph.

Token

API reference

Generated from the component's types — every prop, type, default, and description comes straight from the source. Each part gets its own table; parts that only forward to Base UI list just the props they pass through. Expand a row for the full type and description.

Combobox.Root

Prop

Combobox.Input

Prop

Combobox.ChipsInput

Prop

Combobox.Icon

Prop

Combobox.Trigger

Prop

Combobox.Clear

Prop

Combobox.Content

Prop

Combobox.Status

Prop

Combobox.List

Prop

Combobox.Item

Prop

Combobox.Empty

Prop

Combobox.Group

Prop

Combobox.Separator

Prop

Combobox.GroupLabel

Prop

Combobox.Chips

Prop

Combobox.Chip

Prop

Combobox.ChipRemove

Prop

AttributeDescription
data-slotThe element's slot — the styling/testing anchor.
data-sizeThe control size the input stamps (`sm`/`md`/`lg`); the component layer re-points its metrics off it.
data-popup-openPresent on the input (and trigger) while the popup is open.
data-highlightedPresent on the option row the pointer or keyboard is currently over.
data-selectedPresent on a selected option row (and its indicator).
data-disabledPresent on a disabled option row.
data-list-emptyPresent on the input when its corresponding items list is empty.
data-invalidPresent on the input/chips-input when the field is invalid (set explicitly or by a `Field`).
data-visiblePresent on a `Clear` kept mounted while the field holds a value (drives the fade).