Skip to content

ComboBox

ComboBox is the single-selection, text-searching picker: type to narrow a list of options, move with the arrow keys, pick with Enter. It is for choosing an entity — a customer, a contact, a company — from options that may still be loading, may have failed to load, and may need an “add new” escape hatch.

Use Dropdown for choosing from a known, small set, or several values at once; its filter path is a filterable select, not an entity picker.

import { ComboBox } from '@cratis/components/Common';
const [customerId, setCustomerId] = useState<string | null>(null);
<label htmlFor='customer'>Customer</label>
<ComboBox
id='customer'
options={customers.map(customer => ({ key: customer.id, label: customer.name, description: customer.orgNumber }))}
value={customerId}
onChange={setCustomerId}
placeholder='Search customers'
emptyMessage='No customer matches.'
/>

value is the selected option’s key or null. Selecting writes the option’s label into the input; clearing the text and leaving the field clears the selection. A value set from outside shows its label; options that arrive after mount with a key already chosen show its label too, unless the user is typing at that moment.

filterBehavior
containsDefault. Options whose label contains the typed text, case-insensitively.
startsWithOptions whose label starts with the typed text.
noneoptions shown as given — for a consumer that filters or queries itself.

Typing is what searches. A list opened by the trigger, by ArrowDown or by focus offers every option, even while a selection’s label sits in the input — a selection narrows nothing until the text is edited, so the alternatives to the current choice stay reachable. The first keystroke turns the text into a search again; reopening the list shows everything once more.

onInputChange receives the text as it is typed, so a consumer can drive a server-side search and feed the result back through options with filter='none'. openOnFocus opens the list when the input receives focus, so a short roster is visible before anything is typed.

  • loading with loadingMessage shows the message instead of options, as a status region marked busy.
  • failure replaces the options with the given content as an alert.
  • emptyMessage shows when nothing matches.
  • action renders one row after the options — reachable by arrow keys, chosen with Enter or a click — and calls onAction(inputValue) with the typed text. The selection is unchanged.

The input carries role="combobox" with aria-expanded, aria-controls and aria-activedescendant; the list is a listbox of options; a disabled option is aria-disabled. ArrowDown/ArrowUp move, Home/End jump, Enter selects, Escape closes, typing filters. Name the control through a visible <label htmlFor={id}>, aria-label or aria-labelledby; description and errorMessage render under the control and join aria-describedby; errorMessage also becomes aria-errormessage while invalid.

PropTypeDefaultBehavior
optionsComboBoxOption[]Requiredkey, label, optional description and disabled.
value / onChangestring | null, (key) => voidRequiredControlled selected key.
inputValue / onInputChangestring, (text) => voidInternalControlled or observed input text.
filter'contains' | 'startsWith' | 'none'containsHow typed text narrows options.
openOnFocusbooleanfalseOpens the list on focus.
loading / loadingMessageboolean, ReactNodefalseBusy state instead of options.
failureReactNode—Alert content replacing the options.
emptyMessageReactNode—Shown when nothing matches.
action{ label: string, onAction(inputValue) }—Footer row after the options; the label is its accessible name.
optionLayout'inline' | 'stacked'inlineDescription beside or under the label.
placeholderstring—Empty edit hint.
disabled / readOnly / invalid / requiredbooleanfalseSemantic and visual state.
id / namestringGenerated / —Label association and native form field name (the selected key).
description / errorMessageReactNode—Associated help and invalid-state content.
ptComboBoxParts—Part classes, styles, titles and data attributes.
Typed pt key / DOM partMeaningCanonical states
rootComplete controldisabled, invalid, readonly, loading
inputThe combobox inputdisabled, invalid, readonly, open
triggerThe open buttondisabled, open
popover / listboxPortaled listopen / none
option / optionLabel / optionDescriptionOne option and its textsdisabled, selected / none / none
loading / empty / failureThe three not-ready messagesnone
actionThe footer rownone
description / errorSupporting text, validation messagenone / invalid

The component uses the shared control, surface, overlay, text, highlight, focus and disabled tokens and introduces none of its own. Use parts and tokens rather than internal element order or implementation-library selectors.