build69
build69  /  Components  /  Selection & Choice  /  ComboboxAsync

ComboboxAsync

v1.0.23Updated 20 Oct 2026

Async-loading combobox with debounced fetch, optional creatable entries, multi-select with chips, free-solo input, and grouped results. Exports ComboboxAsync + ComboboxAsyncMulti.

Preview

Live · 10 stories
▸Why this is recommendedcomboboxautocompleteasyncserver-search+4

ComboboxAsync loading its data asynchronously.

Recommended for+14
  • ✓Customer or supplier lookup against a server API
  • ✓Searching a product catalogue too large to preload
  • ✓Owner / assignee pickers backed by a user-search endpoint
  • ✓Address or place autocomplete
  • ✓Pick-or-create a customer while drafting an invoice
  • ✓Any picker over thousands of records that must be queried per keystroke
Tags+6
comboboxautocompleteasyncserver-searchcreatablefree-solotypeaheadremote
Features+4
  • Host-driven async loading via onSearchChange + options
  • Loading spinner and error panel with a Retry button
  • Creatable: "Create '<query>'" row when there is no exact match
  • Free-solo: commit arbitrary typed text as the value
  • Grouped results with headings plus a meta line per option
  • Enter commits the create / free-solo row
  • A selected value keeps its label after the result set moves on — the chosen option is remembered, and `selectedOption` covers a value that was never in a result set
  • Every reset of the internal search is announced through `onSearchChange`, and `onOpenChange` reports open and close
  • Custom trigger via the `trigger` prop
  • Labelled by FormField: id and aria-* land on the role=combobox trigger, which then fills the field column
Not recommended for−2
  • ✕Static option sets already in memory (use Combobox)
  • ✕Selecting multiple values (use ComboboxAsyncMulti)
  • ✕Plain free-text entry with no suggestions (use Input)
  • ✕Command palettes and action menus (use Command)

Install

Ask your agent, or call the MCP tool directly. The resolved bundle is written into your project.

get_registry_item({ name: "ComboboxAsync" })

Usage

import { ComboboxAsync } from '@build69/ui';

<ComboboxAsync
  value={customerId}
  onValueChange={setCustomerId}
  options={results}
  onSearchChange={debouncedSearch}
  loading={isLoading}
  error={error}
  onRetry={refetch}
  creatable
  onCreate={(name) => createCustomer(name)}
  placeholder="Search or create a customer…"
/>

Tick variants in the left menu — each adds a tab with that variant’s real story source, the same code the preview above renders.

Props

PropTypeRequired
options

Current result set — the host replaces it in response to onSearchChange

ComboboxAsyncOption[]yes
onSearchChange

Fires on every keystroke; the host should debounce and fetch. Supplying it also turns off the built-in cmdk filter. Also fires with an empty string whenever the component clears its own search (after a selection, a create or a free-solo commit), so the host never keeps a query the user can no longer see

(query: string) => void | Promise<void>—
onOpenChange

Fires on every open and close of the popover, including the programmatic close after a selection — lets the host drop its query when the box closes without one

(open: boolean) => void—
value

Selected option value (or raw text when freeSolo)

string—
onValueChange

Fires on select; re-selecting the current value clears it

(value: string) => void—
selectedOption

The option the current value stands for. The trigger falls back to its label when the value is not in the current options — needed only for a value the component has never seen, such as a form that mounts with a saved value. Ignored unless its value matches the current value, and the current options always win

ComboboxAsyncOption | null—
loading

Show the loading spinner in the list

boolean—
error

Error message; replaces the list with an error panel

string—
onRetry

Shows a Retry button inside the error panel

() => void—
creatable

Show a "Create '<query>'" row when no option matches exactly

boolean—
onCreate

Called with the query on create; the host owns value assignment. Without it, the query is committed as the value

(query: string) => void—
freeSolo

Allow committing any typed text as the value

boolean—
placeholder

Trigger text when nothing is selected (default "Select…")

string—
trigger

Replace the default trigger button; the forwarded ref stays on the default trigger

React.ReactNode—
disabled

Disable the trigger

boolean—
scrollbar

How the option list draws its scrollbar once it overflows — forwarded to CommandList. Default 'thin': a visible themed bar, because the height cap is the only thing hiding the remaining options

'thin' | 'hidden' | 'auto'—
listMaxHeight

Cap the option list's height (number = px, string = any CSS length such as '50vh'). Omitted, the list keeps CommandList's 18rem default

number | string—
id

DOM id set on the trigger — a <label htmlFor> (or FormField) names the control through it

string—
aria-label

Accessible name for the trigger when there is no visible label

string—
aria-labelledby

Id(s) of the visible label element(s) that name the trigger

string—
aria-describedby

Id(s) of hint or error text that describe the trigger

string—
aria-invalid

Mark the trigger invalid — FormField clones this in from its error prop

boolean—
aria-required

Mark the trigger required — FormField clones this in from its required prop

boolean—
invalid

Mark the field invalid (destructive ring); aria-invalid wins when both are set

boolean—
required

Mark the field required (aria-required on the trigger)

boolean—
className

Class applied to the trigger; a width here wins over the default (w-full inside a FormField, the standalone width otherwise)

string—

Related