Skip to content
Nizivo UI

Combobox

A text input that suggests items from a list as the reader types, such as a country in a checkout form or search suggestions. Input's box, with Select's popup and rows; React Aria's ComboBox inside.

Client componentEngine: react-aria

Import

import { Combobox } from '@nizivo/react/combobox';

// Once per app (Getting started), after
// @nizivo/tokens/tokens.css and @nizivo/react/css/base.css and @nizivo/react/css/input.css
import '@nizivo/react/css/combobox.css';

Static or interactive

Client. It is React Aria's ComboBox, which reads its Field through FieldScope, so a page with a Combobox also loads FieldScope. The server HTML is the input with the chosen item's text and, with a name, the hidden input with its value, so a form submits before hydration; the list opens once the page has hydrated. How to choose.

In a client app (a Vite or React Router single-page app, or a 'use client' file) it is an ordinary React component: event handlers, refs and state work as usual. In a client app.

Parts

Combobox.Root holds the value and draws the box. Combobox.Input is the text input; Combobox.Trigger after it adds a chevron button that opens every item. Combobox.Popup holds Combobox.Items, which may sit in Combobox.Groups with a Combobox.GroupLabel, apart from each other by a Combobox.Separator. The popup is as wide as the box, the Trigger included.

Value and text

value is the chosen item's value, and inputValue the text in the input; each has a default… and an on…Change. Choosing an item puts its text (textValue, or its children when they are a string) in the input. Without allowsCustomValue, a blur or Escape puts back the chosen item's text, and clearing the input clears the choice. With it, the text may be anything: it stays as typed, and a form submits it.

When the list opens

menuTrigger is 'input' by default: the list opens as the reader types and shows the items that contain the text, matched in the locale's collation and ignoring case. 'focus' also opens it, with every item, when the input takes focus or is pressed. 'manual' opens it only from the Trigger or the arrow keys. With no match the list closes. defaultOpen opens it on the first render; React Aria keeps the open state itself, so there is no controlled open, and onOpenChange reports each change.

Forms

With name (or a Field's), a hidden input carries the chosen item's value, de and never "Germany", into FormData; with allowsCustomValue the input itself carries the typed text. Inside a Field, required is checked natively on the input, so the Field's messages.valueMissing applies.

Engine-owned

The Trigger's name, "Show suggestions" followed by the field's label, the list's name, "Suggestions" (used only when the field has no label; with one, the list is named by it), and the announcements of how many items are available come from React Aria's catalogue, translated in 36 locales. labels does not set them: the Strings guide lists the engine-owned strings.

Examples

Basic

A country in a checkout form: type to filter; the form gets the chosen code.

Where we ship the order.
import type { JSX } from 'react';
import { Field } from '@nizivo/react/field';
import { Combobox } from '@nizivo/react/combobox';

export function ComboboxExample(): JSX.Element {
  return (
    <Field name="country" label="Country" description="Where we ship the order." required>
      <Combobox.Root>
        <Combobox.Input placeholder="Start typing" />
        <Combobox.Trigger />
        <Combobox.Popup>
          <Combobox.Item value="at">Austria</Combobox.Item>
          <Combobox.Item value="be">Belgium</Combobox.Item>
          <Combobox.Item value="de">Germany</Combobox.Item>
          <Combobox.Item value="fr">France</Combobox.Item>
          <Combobox.Item value="in">India</Combobox.Item>
          <Combobox.Item value="nl">Netherlands</Combobox.Item>
          <Combobox.Item value="gb">United Kingdom</Combobox.Item>
          <Combobox.Item value="us">United States</Combobox.Item>
        </Combobox.Popup>
      </Combobox.Root>
    </Field>
  );
}

Suggestions

Search suggestions, grouped, with any text allowed: the form gets what was typed.

import type { JSX } from 'react';
import { Field } from '@nizivo/react/field';
import { Combobox } from '@nizivo/react/combobox';

export function ComboboxSuggestionsExample(): JSX.Element {
  return (
    <Field name="q" label="Search products">
      <Combobox.Root allowsCustomValue>
        <Combobox.Input placeholder="Mugs, bowls, glaze…" />
        <Combobox.Popup>
          <Combobox.Group>
            <Combobox.GroupLabel>Recent</Combobox.GroupLabel>
            <Combobox.Item value="sand-mug">Sand mug</Combobox.Item>
          </Combobox.Group>
          <Combobox.Separator />
          <Combobox.Group>
            <Combobox.GroupLabel>Categories</Combobox.GroupLabel>
            <Combobox.Item value="mugs">Mugs</Combobox.Item>
            <Combobox.Item value="bowls">Bowls</Combobox.Item>
            <Combobox.Item value="plates">Plates</Combobox.Item>
          </Combobox.Group>
        </Combobox.Popup>
      </Combobox.Root>
    </Field>
  );
}

States

A chosen value, sizes, an error from the server and disabled.

Express is not available for this address.
import type { JSX } from 'react';
import { Field } from '@nizivo/react/field';
import { Combobox } from '@nizivo/react/combobox';

export function ComboboxStatesExample(): JSX.Element {
  const items = (
    <Combobox.Popup>
      <Combobox.Item value="standard">Standard</Combobox.Item>
      <Combobox.Item value="express">Express</Combobox.Item>
      <Combobox.Item value="pickup" disabled>
        Store pickup
      </Combobox.Item>
    </Combobox.Popup>
  );
  return (
    <div style={{ display: 'grid', gap: 16, maxInlineSize: 320 }}>
      <Field label="Shipping">
        <Combobox.Root defaultValue="express" size="sm">
          <Combobox.Input />
          <Combobox.Trigger />
          {items}
        </Combobox.Root>
      </Field>
      <Field label="Shipping" error="Express is not available for this address.">
        <Combobox.Root defaultValue="express" size="lg">
          <Combobox.Input />
          <Combobox.Trigger />
          {items}
        </Combobox.Root>
      </Field>
      <Field label="Shipping" disabled>
        <Combobox.Root defaultValue="standard">
          <Combobox.Input />
          <Combobox.Trigger />
          {items}
        </Combobox.Root>
      </Field>
    </div>
  );
}

Parts

Use the parts through the namespace (Combobox.Root) or by their flat names (ComboboxRoot); both work from Server Components.

Combobox.Root

Client component Renders data-slot="combobox" with the class nz-combobox. Props: ComboboxRootProps. Source: src/combobox/combobox.client.tsx.

NameTypeDefaultDescription
allowsCustomValuebooleanfalse

The text may be any value, not only an item's: on blur it stays as typed, and a form submits the text instead of the chosen item's value.

aria-labelstring–
aria-labelledbystring–

Inside a Field, the Field label's id, which also names the list and the Trigger.

childrenrequiredReactNode–

Combobox.Input, an optional Combobox.Trigger, and Combobox.Popup with the items.

classNamestring–
defaultInputValuestring–
defaultOpenboolean–

Open on the first render, showing every item. React Aria keeps the open state itself, so there is no controlled open; onOpenChange reports each change.

defaultValuestring | null–
disabledboolean–
idstring–

The input's id. Inside a Field, the Field's, which its label points at.

inputValuestring–

The text in the input (controlled).

invalidboolean–

An error message is presented (owned by Field, never by the engine). Inside a Field, the Field sets it.

menuTriggerComboboxMenuTrigger
"input""focus""manual"
'input'
namestring–

With name, a hidden input carries the chosen item's value into FormData (the text, with allowsCustomValue). Inside a Field, the Field's name, required, disabled, readOnly and presented error apply unless set here (an explicit false wins).

onInputChange(inputValue: string) => void–

Every change of the text: typed, cleared, or set to the chosen item's text.

onOpenChange(open: boolean, details: ChangeDetails<OpenReason>) => void–

details.reason is 'unknown' with React Aria, which reports no cause.

onValueChange(value: string | null, details: ChangeDetails<ComboboxValueReason>) => void–

details.reason is 'unknown' with React Aria, which reports no cause.

readOnlyboolean–
requiredboolean–
sizeControlSize
"sm""md""lg"
'md'

32 / 40 / 48 px, as Input.

valuestring | null–

The chosen item's value; null is none.

Combobox.Input

Client component Renders data-slot="combobox-input" with the class nz-combobox-input. Props: ComboboxInputProps. Source: src/combobox/combobox.client.tsx.

NameTypeDefaultDescription
renderRenderProp<ComboboxInputState>–

Must render an <input>.

  • Also takes every attribute of <input> except children, defaultValue, disabled, id, name, readOnly, required, size, type, value. (Omit<ComponentPropsWithRef<"input">, "children" | "value" | "defaultValue" | "type" | "size" | "id" | "name" | "required" | "disabled" | "readOnly">)

render takes an element, or, in client code, a function of the props and this state (ComboboxInputState):

NameTypeDefaultDescription
disabledrequiredboolean–
focusVisiblerequiredboolean–
openrequiredboolean–

The list is open (data-open on the input, as on a trigger).

Combobox.Trigger

Client component Renders data-slot="combobox-trigger" with the class nz-combobox-trigger. Props: ComboboxTriggerProps. Source: src/combobox/combobox.client.tsx.

NameTypeDefaultDescription
childrenReactNode–

Replaces the chevron glyph. Decorative.

renderRenderProp<ComboboxTriggerState>–

Must render a <button>.

  • Also takes every attribute of <button> except children, disabled, type. (Omit<ComponentPropsWithRef<"button">, "children" | "disabled" | "type">)

render takes an element, or, in client code, a function of the props and this state (ComboboxTriggerState):

NameTypeDefaultDescription
disabledrequiredboolean–
openrequiredboolean–
pressedrequiredboolean–

Combobox.Popup

Client component Renders data-slot="combobox-popup" with the class nz-combobox-popup. Props: ComboboxPopupProps. Source: src/combobox/combobox.client.tsx.

NameTypeDefaultDescription
alignAlign
"start""center""end"
'start'
childrenrequiredReactNode–
classNamestring–
containerElement | null–

Portal container. Defaults to the parent overlay, else document.body (§12.1 L2).

side"top" | "bottom"'bottom'
sideOffsetnumber4

Distance from the box in px.

styleCSSProperties–

Combobox.Item

Client component Renders data-slot="combobox-item" with the class nz-combobox-item. Props: ComboboxItemProps. Source: src/combobox/combobox.client.tsx.

NameTypeDefaultDescription
childrenrequiredReactNode–
disabledboolean–
indicatorReactNode–

Replaces the check shown at the end of the chosen item; null shows none. Decorative.

renderRenderProp<ComboboxItemState>–

Must render a <div>.

textValuestring–

The text the input shows and the filter matches; defaults to children when it is a string.

valuerequiredstring–
  • Also takes every attribute of <div> except children, id. (Omit<ComponentPropsWithRef<"div">, "children" | "id">)

render takes an element, or, in client code, a function of the props and this state (ComboboxItemState):

NameTypeDefaultDescription
disabledrequiredboolean–
focusVisiblerequiredboolean–
highlightedrequiredboolean–

Under the pointer, or the active item of the keyboard (the input keeps focus).

selectedrequiredboolean–

Combobox.Group

Client component Renders data-slot="combobox-group" with the class nz-combobox-group. Props: ComboboxGroupProps. Source: src/combobox/combobox.client.tsx.

NameTypeDefaultDescription
renderRenderProp<NoState>–

Must render a <section>.

  • Also takes every attribute of <section> except id. (Omit<ComponentPropsWithRef<"section">, "id">)

render takes an element, or, in client code, a function of the props and no state.

Combobox.GroupLabel

Client component Renders data-slot="combobox-group-label" with the class nz-combobox-group-label. Props: ComboboxGroupLabelProps. Source: src/combobox/combobox.client.tsx.

NameTypeDefaultDescription
renderRenderProp<NoState>–

Must render a <header>.

  • Also takes every attribute of <header> except id. (Omit<ComponentPropsWithRef<"header">, "id">)

render takes an element, or, in client code, a function of the props and no state.

Combobox.Separator

Client component Renders data-slot="combobox-separator" with the class nz-combobox-separator. Props: ComboboxSeparatorProps. Source: src/combobox/combobox.client.tsx.

NameTypeDefaultDescription
renderRenderProp<NoState>–

Must render a <div>.

  • Also takes every attribute of <div> except children. (Omit<ComponentPropsWithRef<"div">, "children">)

render takes an element, or, in client code, a function of the props and no state.

Other exports

ComboboxGroupLabelProps type

import type { ComboboxGroupLabelProps } from '@nizivo/react/combobox'

The props of Combobox.GroupLabel, listed above.

ComboboxGroupProps type

import type { ComboboxGroupProps } from '@nizivo/react/combobox'

The props of Combobox.Group, listed above.

ComboboxInputProps type

import type { ComboboxInputProps } from '@nizivo/react/combobox'

The props of Combobox.Input, listed above.

ComboboxInputState type

import type { ComboboxInputState } from '@nizivo/react/combobox'

The render state of Combobox.Input, listed above.

ComboboxItemProps type

import type { ComboboxItemProps } from '@nizivo/react/combobox'

The props of Combobox.Item, listed above.

ComboboxItemState type

import type { ComboboxItemState } from '@nizivo/react/combobox'

The render state of Combobox.Item, listed above.

ComboboxMenuTrigger type

import type { ComboboxMenuTrigger } from '@nizivo/react/combobox'

When the list opens. 'input': as the reader types (the default). 'focus': also when the input takes focus, showing every item. 'manual': only from the Trigger or Arrow Down / Up.

type ComboboxMenuTrigger = "input" | "focus" | "manual"

ComboboxPopupProps type

import type { ComboboxPopupProps } from '@nizivo/react/combobox'

The props of Combobox.Popup, listed above.

ComboboxPopupState type

import type { ComboboxPopupState } from '@nizivo/react/combobox'

NameTypeDefaultDescription
enteringrequiredboolean–
exitingrequiredboolean–
siderequiredSide
"top""right""bottom""left"
–
ComboboxRootProps type

import type { ComboboxRootProps } from '@nizivo/react/combobox'

The props of Combobox.Root, listed above.

ComboboxRootState type

import type { ComboboxRootState } from '@nizivo/react/combobox'

NameTypeDefaultDescription
disabledrequiredboolean–
invalidrequiredboolean–
openrequiredboolean–
readOnlyrequiredboolean–
requiredrequiredboolean–
ComboboxSeparatorProps type

import type { ComboboxSeparatorProps } from '@nizivo/react/combobox'

The props of Combobox.Separator, listed above.

ComboboxTriggerProps type

import type { ComboboxTriggerProps } from '@nizivo/react/combobox'

The props of Combobox.Trigger, listed above.

ComboboxTriggerState type

import type { ComboboxTriggerState } from '@nizivo/react/combobox'

The render state of Combobox.Trigger, listed above.

ComboboxValueReason type

import type { ComboboxValueReason } from '@nizivo/react/combobox'

React Aria reports no cause for a change.

type ComboboxValueReason = "unknown"

Data attributes and slots

Style parts by class (.nz-<slot>) and state by data attribute. Attribute meanings come from contracts/vocabulary.json.

SlotRendered byData attributes
combobox
.nz-combobox
Combobox.Root
root (no render)
  • data-disabled (present or absent) ← disabled
    Mirrors the disabled attribute
  • data-invalid (present or absent) ← invalid
    An error message is currently presented (not "fails constraints"); always the Field's own state, never the engine's
  • data-open (present or absent)
    Open
  • data-readonly (present or absent)
    Mirrors the readonly attribute
  • data-required (present or absent) ← required
    Mirrors the required attribute
  • data-size: "sm""md""lg" ← size
    The size prop; values in each component manifest
combobox-control
.nz-combobox-control
Combobox.Root
inner element (CSS only); interactive, focus: within
  • data-disabled (present or absent) ← disabled
    Mirrors the disabled attribute
  • data-invalid (present or absent) ← invalid
    An error message is currently presented (not "fails constraints"); always the Field's own state, never the engine's
  • data-readonly (present or absent)
    Mirrors the readonly attribute
  • data-size: "sm""md""lg" ← size
    The size prop; values in each component manifest
combobox-group
.nz-combobox-group
Combobox.Group
root (takes render)
none
combobox-group-label
.nz-combobox-group-label
Combobox.GroupLabel
root (takes render)
none
combobox-input
.nz-combobox-input
Combobox.Input
root (takes render)
  • data-open (present or absent)
    Open
combobox-item
.nz-combobox-item
Combobox.Item
root (takes render); interactive, focus: engine; forced colours: highlight on [data-selected]
  • data-disabled (present or absent) ← disabled
    Mirrors the disabled attribute
  • data-focus-inset (present or absent)
    a11y-floor opt-out: draw the focus outline inside the part (outline-offset -2px), for clipped scrollers such as DialogBody and table cells (§8.5)
  • data-focus-visible (present or absent)
    Focus is visible; elsewhere use :focus-visible
  • data-highlighted (present or absent)
    Keyboard or virtual focus, or pointer hover
  • data-selected (present or absent)
    Selected
combobox-list
.nz-combobox-list
Combobox.Popup
inner element (CSS only)
none
combobox-popup
.nz-combobox-popup
Combobox.Popup
root (no render); forced colours: border
  • data-align: "start""center""end" ← align
    Resolved alignment
  • data-entering (present or absent)
    Enter transition running; the node stays mounted until it ends
  • data-exiting (present or absent)
    Exit transition running; the node stays mounted until it ends
  • data-open (present or absent)
    Open
  • data-side: "top""right""bottom""left"
    Resolved physical side after flip and RTL; the side prop takes logical values
combobox-separator
.nz-combobox-separator
Combobox.Separator
root (takes render)
none
combobox-trigger
.nz-combobox-trigger
Combobox.Trigger
root (takes render); interactive, focus: self
  • data-disabled (present or absent)
    Mirrors the disabled attribute
  • data-open (present or absent)
    Open
  • data-pressed (present or absent)
    Pointer or keyboard press (native parts use :active)

CSS hooks

Set a hook on the component's class or any ancestor to restyle it without specificity games; leave it unset for the default shown.

HookDefaultRead as
--nz-combobox-trigger-fg--nz-text-secondary
  • color on .nz-combobox-trigger

Layers: nz.components. Classes: .nz-combobox, .nz-combobox-chevron, .nz-combobox-control, .nz-combobox-group, .nz-combobox-group-label, .nz-combobox-item, .nz-combobox-item-indicator, .nz-combobox-list, .nz-combobox-popup, .nz-combobox-separator, .nz-combobox-trigger, .nz-input. Runtime variables: --nz-available-height, --nz-transform-origin, --nz-trigger-width.

Tokens this stylesheet reads (34)

--nz-bg-brand-subtle, --nz-bg-hover, --nz-bg-pressed, --nz-bg-surface-raised, --nz-border-subtle, --nz-border-width-default, --nz-border-width-focus, --nz-duration-fade, --nz-duration-fast, --nz-duration-spring, --nz-easing-enter, --nz-easing-exit, --nz-easing-spring, --nz-easing-standard, --nz-elevation-4, --nz-font-label-m, --nz-font-label-m-strong, --nz-font-label-s, --nz-icon-brand, --nz-radius-md, --nz-radius-sm, --nz-scale-enter, --nz-size-icon-sm, --nz-space-1, --nz-space-1-5, --nz-space-2, --nz-space-3, --nz-space-6, --nz-text-disabled, --nz-text-primary, --nz-text-secondary, --nz-tracking-label-m, --nz-tracking-label-s, --nz-z-overlay

Accessibility

These parts are interactive. Each gets the focus outline and touch-action rule from @nizivo/react/css/base.css (the nz.base layer), so the floor survives a restyle:

  • combobox-control: draws the outline when the field inside it has focus.
  • combobox-item: draws the focus outline when the engine reports keyboard focus, not on pointer hover.
  • combobox-trigger: draws the focus outline itself on keyboard focus.

In forced-colours mode these states keep a visible system colour: combobox-item[data-selected] (highlight), combobox-popup (border).

Behaviour comes from the react-aria engine through Nizivo's adapter: focus management, ARIA wiring and keyboard handling are the engine's.

Keyboard

KeysAction
TabMoves focus to the input. The Trigger is skipped; Tab out of the input closes the list.
TypingOpens the list with the items that contain the text, in any case.
Arrow Down, Arrow UpOpen the list, then move the active item. Focus stays in the input, which names the active item for a screen reader.
EnterChooses the active item and closes the list.
EscapeCloses the list and puts back the chosen item's text (with allowsCustomValue, the typed text stays).

Source: React Aria Components 1.22.1 ComboBox (_engines/react-aria/combobox.client.tsx). The overlay lane (test/overlay/combobox.spec.tsx) runs the family's overlay conformance and checks filtering, the active item, Enter, Escape, the Trigger, blur, custom values, forms and a Field's required message.

Figma

Node ids from design/component-map.json, which nz gen writes from component.json. The links open the Nizivo Figma library, which may ask you to sign in.

  • Canonical: none
  • Ghost masters: none

On this page