ComboBox
Search and select
Selected source: alpha
const [station, setStation] = useState<string | null>(null);
<ComboBox
label="STATION"
options={[
{ value: "alpha", label: "Alpha station" },
{ value: "bravo", label: "Bravo station" },
]}
value={station}
onValueChange={setStation}
placeholder="Search stations…"
/>;
Use unique, stable option values. Clearing the input selects null; unmatched queries
do not create options. Labels keep authored case, and a long label wraps. defaultValue
enables uncontrolled selection; the forwarded ref targets the input.
The suggestions open at least as wide as the field, wrap at about 30rem, keep 16px from the screen edges, and long options wrap, so an identifier never runs the list off a phone screen.
Sizes and states
Choose an available station.
Loading options.
Loading options.
Disabled options stay visible but cannot be selected. name submits the selected value.
Caller-supplied results
Matching defaults to a language-sensitive contains search. For caller-filtered results,
set filter={false}, control the query with inputValue / onInputChange, and supply
options. Query and selection state are independent, but keep the selected option in
options (include it in your results): a selected value with no matching option shows an
empty field while the form still submits the value. In development the ComboBox reports
such a value once, unless you own the query and its results (filter={false} with
inputValue), where results change while the operator types.
<ComboBox
label="REMOTE STATION"
options={results}
inputValue={query}
onInputChange={setQuery}
value={station}
onValueChange={setStation}
filter={false}
loading={loading}
emptyMessage="No stations found."
/>
Loading leaves supplied options selectable, and "Loading options." is announced politely
when loading starts. The caller owns cancellation and stale results; use disabled to
pause interaction (a disabled field keeps its value readable: dim text on a muted fill).
Multiselect is not supported.
Motion
The popup arrives from its trigger in 200ms and exits in 150ms, with a small directional movement and scale. The chevron rotates up while open and returns down on close using the same timing. Selection and callbacks update immediately. Typing, keyboard navigation, Escape, assistive activation, and system reduced motion stay instant. Use motion="none" to disable the treatment.
Props
| PROP | TYPE | DEFAULT | REQUIRED |
|---|---|---|---|
motionTactile pointer arrival, exit, and open-state chevron rotation; keyboard, assistive activation, and system reduced motion remain immediate. | enumauto | none | "auto" | no |
optionsAvailable options. Values must be unique and stable; labels preserve authored case. | array | — | yes |
valueControlled selected option value, or null to clear selection. Omit for internal selection state. | string | — | no |
defaultValueInitial selected option value for uncontrolled use. | string | — | no |
onValueChange(value: string | null) => void. Called on option selection or when clearing the input clears the selected value. | function | — | no |
inputValueControlled search input text; independent from the selected value. | string | — | no |
onInputChange(text: string) => void. Use with inputValue for caller-owned search and asynchronous results. | function | — | no |
labelVisible accessible label. Alternatively, use Field or aria-label. | string | — | no |
aria-labelAccessible name when there is no visible label or surrounding Field. | string | — | no |
aria-describedbyDescription IDs; otherwise inherits the surrounding Field help or error message. | string | — | no |
placeholder | string | — | no |
disabled | boolean | false | no |
required | boolean | false | no |
size | enumsm | md | lg | "md" | no |
invalidError appearance and aria-invalid; otherwise inherits Field error state. | boolean | false | no |
loadingAnnounces loading and marks input busy. Supplied options remain selectable unless disabled. | boolean | false | no |
filterLanguage-sensitive contains matching by default. Set false when the caller already filters options or supplies asynchronous results. | boolean | true | no |
emptyMessage | string | "No options found." | no |
nameForm field name; submits the selected option value. | string | — | no |
idInput ID; otherwise inherits Field or React Aria's generated ID. | string | — | no |
Accessibility
- Name the input with
label,aria-label, or Field; Field help, errors, invalid and required state are inherited. Anaria-labelalso names the suggestions button and the list ("Show suggestions Origin station"), so two such fields are told apart. - Arrow keys navigate enabled options; Enter selects; Escape dismisses.
- Selection has a checkmark; options have visible focus and at least 24px targets.
- Empty and loading states have text; loading marks the input busy.
- The field and its button use the control outline (
--oprt-border-control, 3:1 or more). - In forced-colors (high contrast) mode, focus draws a 2px outline in the system text color around the field and its button, and the option about to be chosen is drawn in the system highlight color.
Manifest entry
<ComboBox
label="STATION"
options={[{ value: "alpha", label: "Alpha station" }, { value: "bravo", label: "Bravo station" }]}
value={station}
onValueChange={setStation}
placeholder="Search stations…"
/>View agent manifest
{
"description": "Searchable single-value selection with keyboard navigation and caller-supplied asynchronous results.",
"props": {
"motion": {
"type": "enum",
"values": [
"auto",
"none"
],
"default": "auto",
"description": "Tactile pointer arrival, exit, and open-state chevron rotation; keyboard, assistive activation, and system reduced motion remain immediate."
},
"options": {
"type": "array",
"items": {
"value": "string (required, unique)",
"label": "string (required)",
"disabled": "boolean (default false)"
},
"required": true,
"description": "Available options. Values must be unique and stable; labels preserve authored case."
},
"value": {
"type": "string",
"description": "Controlled selected option value, or null to clear selection. Omit for internal selection state."
},
"defaultValue": {
"type": "string",
"description": "Initial selected option value for uncontrolled use."
},
"onValueChange": {
"type": "function",
"description": "(value: string | null) => void. Called on option selection or when clearing the input clears the selected value."
},
"inputValue": {
"type": "string",
"description": "Controlled search input text; independent from the selected value."
},
"onInputChange": {
"type": "function",
"description": "(text: string) => void. Use with inputValue for caller-owned search and asynchronous results."
},
"label": {
"type": "string",
"description": "Visible accessible label. Alternatively, use Field or aria-label."
},
"aria-label": {
"type": "string",
"description": "Accessible name when there is no visible label or surrounding Field."
},
"aria-describedby": {
"type": "string",
"description": "Description IDs; otherwise inherits the surrounding Field help or error message."
},
"placeholder": {
"type": "string"
},
"disabled": {
"type": "boolean",
"default": false
},
"required": {
"type": "boolean",
"default": false
},
"size": {
"type": "enum",
"values": [
"sm",
"md",
"lg"
],
"default": "md"
},
"invalid": {
"type": "boolean",
"default": false,
"description": "Error appearance and aria-invalid; otherwise inherits Field error state."
},
"loading": {
"type": "boolean",
"default": false,
"description": "Announces loading and marks input busy. Supplied options remain selectable unless disabled."
},
"filter": {
"type": "boolean",
"default": true,
"description": "Language-sensitive contains matching by default. Set false when the caller already filters options or supplies asynchronous results."
},
"emptyMessage": {
"type": "string",
"default": "No options found."
},
"name": {
"type": "string",
"description": "Form field name; submits the selected option value."
},
"id": {
"type": "string",
"description": "Input ID; otherwise inherits Field or React Aria's generated ID."
}
},
"composable_with": [
"Field",
"DataGrid"
],
"never_inside": [
"Button",
"Badge"
],
"example": "<ComboBox\n label=\"STATION\"\n options={[{ value: \"alpha\", label: \"Alpha station\" }, { value: \"bravo\", label: \"Bravo station\" }]}\n value={station}\n onValueChange={setStation}\n placeholder=\"Search stations…\"\n/>"
}