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.

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.
failureReactNodeAlert content replacing the options.
emptyMessageReactNodeShown 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.
placeholderstringEmpty edit hint.
disabled / readOnly / invalid / requiredbooleanfalseSemantic and visual state.
id / namestringGenerated / —Label association and native form field name (the selected key).
description / errorMessageReactNodeAssociated help and invalid-state content.
ptComboBoxPartsPart 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.