Skip to main content

Design system components

Combobox

Single-select combobox with portaled dropdown, shared with MultiCombobox.

Status: stable. Group: form. Markdown version: /design-system/components/combobox.md

When to use

Combobox is the styled single-select for editorial forms: a button trigger that opens a portaled listbox with the full APG keyboard model. It is controlled; pair value with onValueChange. It owns its field chrome (label, error, helperText), and shares its look and dropdown behavior with MultiCombobox. Reach for it when the native Select styling is not enough; the option list stays flat strings only.

When not to use

  • Do not nest inside containers with overflow: hidden without portaling (dropdown renders on document.body).
  • Do not use Combobox for free text with suggestions; it only emits option values (no free-solo mode). Use TextInput plus your own suggestion list.
  • Do not use it for multiple selection; MultiCombobox owns chips and type-to-filter.

Look-alikes

  • MultiCombobox: Do not use it for multiple selection; MultiCombobox owns chips and type-to-filter.
  • TextInput: Do not use Combobox for free text with suggestions; it only emits option values (no free-solo mode). Use TextInput plus your own suggestion list.

Props

Combobox props
PropTypeDefaultDescriptionRequired
labelstringnoneField label; associates the trigger.Yes
optionsComboboxOption[]noneSelectable options.Yes
valuestringnoneSelected option value (controlled); "" shows the placeholder.Yes
onValueChange(value: string) => voidnoneCalled with the chosen value.Yes
idstringnoneExplicit control id (wires the label); auto-generated when omitted.No
placeholderstringnoneShown when no value is selected.No
helperTextstringnoneAssistive text below the field.No
errorstringnoneError message; renders above the helper line and sets aria-invalid.No
statusFieldStatusnoneSemantic state: colours the border and styles statusMessage. error overrides it.No
statusMessagestringnoneMessage under the field for status, above the helper line.No
actionsFieldAction[]noneIcon buttons at the end of the label row: each runs an operation or opens a menu.No
requiredbooleanfalseMarks the field required.No
disabledbooleanfalseDisables the control.No
classNamestringnoneClasses on the field wrapper.No
sizeFieldSizemdField size shared by every DS field: 40, 48 or 56px tall.No
namestringnoneSubmits the value with a surrounding form, through a hidden input.No
aria-describedbystringnoneExtra ids the trigger is described by, after the status and helper text.No
searchablebooleanfalseMakes the field a text input that filters the list as you type (every typed word matches the start of a word in an option's label or keywords). Use it for long lists, such as time zones or countries; the forwarded ref then points at the input.No

Variants

  • Available sizes: sm, md, lg (default md).

Invalid combinations

The contract records no invalid combinations.

Keyboard

Combobox keyboard behaviour
KeyBehaviour
TabMoves focus to and from the trigger and closes an open list. Focus moving to any element outside the field also closes it; options are never Tab stops.
EnterActivates the focused control. (standard)
SpaceActivates or toggles the focused control. (standard)
EscapeCloses or dismisses the open layer. (standard)
ArrowDownOpens the list, then moves the highlight down to the next enabled option with wrap-around. Disabled options are skipped.
ArrowUpOpens the list, then moves the highlight up to the previous enabled option with wrap-around. Disabled options are skipped.
HomeOpens the list if needed and moves the highlight to the first enabled option.
EndOpens the list if needed and moves the highlight to the last enabled option.
GeneralPrintable characters (typeahead): open the list and move the highlight to the first option whose label, or any word in it, starts with what was typed; the search clears after a 0.7 s pause, and a space inside a search is part of it.

Rows marked (standard) name a key the contract lists without a description; the behaviour shown is the usual one for that key.

Rules

Every item under When not to use is a MUST NOT. MUST and SHOULD follow RFC 2119.

Combobox rules
LevelRuleHow it is checked
MUSTARIA: Trigger role=combobox with aria-expanded/aria-controls/aria-activedescendant.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTARIA: Labelled via aria-labelledby.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTARIA: Listbox with role=listbox + aria-label.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTARIA: Options role=option with aria-selected.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTARIA: Error sets aria-invalid + aria-describedby.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTRequired stories produce no axe violations at the configured severity.Automated by npm run test:stories:matrix:ci. WCAG 2.5.8.
MUSTThe accessibility tree (role, name, state) matches the committed snapshot for every required story.Automated by npm run test:stories:matrix:ci. WCAG 1.3.1, 4.1.2.
MUSTThe component remains legible and operable under a real browser forced-colors: active pass.Automated by npm run test:stories:hc.
MUSTEvery documented keyboard interaction works as specified.Not yet verified: no evidence record. WCAG 2.1.1, 2.1.2.
MUSTA human completed an end-to-end accessibility review of this component.Manual review, last on 2026-11-04.
SHOULDAlways control it: pass value and onValueChange together; there is no uncontrolled mode.Not stated in the contract.
SHOULDPrefer native Select for plain forms; Combobox earns its cost when the design needs the styled dropdown or per-option disabling.Not stated in the contract.
SHOULDLoad async options into state and pass the current array; the empty state renders a localized "no matching options" row on its own.Not stated in the contract.
SHOULDSet placeholder for the empty value so the closed trigger never renders blank.Not stated in the contract.
SHOULD NOTDo not render it inside overflow-hidden containers without accepting the portal: the listbox mounts on document.body and positions itself.Not stated in the contract.

Install and import

  • Install: npm install @digitaltableteur/react (version 0.9.1, restricted access on npm).
  • Import: import { Combobox } from "@digitaltableteur/react";
  • Entry point: @digitaltableteur/react/forms exports it too.
  • Styles: import @digitaltableteur/react/style.css once at the app boundary.
  • Also available as the custom element <dt-combobox>; see the custom elements manifest.