Combobox

A text field that filters a list as you type. The field is Koala's Input and the list is Select's panel, so it sits beside both without a seam. Autocomplete is the free-text sibling.

Installation#

# One-time setup (tokens + lib helpers)npx koalaui-cli@latest init# Add this component (its dependencies come along)npx koalaui-cli@latest add combobox

Usage#

usage.tsxTSX
import {  Combobox, ComboboxInput, ComboboxContent, ComboboxItem, ComboboxEmpty,} from "@/components/ui/combobox"export function Example() {  const [value, setValue] = useState("")  return (    <Combobox value={value} onValueChange={setValue}>      <ComboboxInput aria-label="Framework" placeholder="Pick a framework" />      <ComboboxContent>        <ComboboxItem value="next">Next.js</ComboboxItem>        <ComboboxItem value="remix">Remix</ComboboxItem>        <ComboboxEmpty />      </ComboboxContent>    </Combobox>  )}

Anatomy#

import {  Combobox, Autocomplete, ComboboxInput, ComboboxContent, ComboboxItem,  ComboboxGroup, ComboboxLabel, ComboboxSeparator, ComboboxEmpty, ComboboxStatus,} from "@/components/ui/combobox"<Combobox>            {/* or <Autocomplete> */}  <ComboboxInput />  <ComboboxContent>    <ComboboxGroup>      <ComboboxLabel />      <ComboboxItem />    </ComboboxGroup>    <ComboboxSeparator />    <ComboboxStatus />    <ComboboxEmpty />  </ComboboxContent></Combobox>

Icons and flags#

Rows take a leading icon like Select rows do, muted beside a medium label. Pass the picked option's glyph to prefix so the field shows it too. For countries, use Flag and match on the ISO code with keywords.

Groups#

ComboboxGroup with a ComboboxLabel heading, split by ComboboxSeparator. A group hides once all its rows filter out, and separators step aside while you type, so no orphan rules are left.

Clearable#

The clear button shows while the field holds text. It stays out of the tab order because Escape does the same from the keyboard. Turn it off with showClear={false}.

Control inputValue to fetch as the user types, and pass filter={null} so the server's results show as they are. ComboboxStatus holds the list's place while a request is in flight and is announced politely.

Creatable#

Add a row for the typed text when nothing matches it exactly. Its textValue is the query, so the filter always keeps it, and picking it commits the new value like any other row.

Autocomplete#

Autocomplete takes the same parts, but the text is the value: anything typed is valid, and a suggestion only fills it in. Nothing is highlighted until you arrow down, so Enter keeps what you wrote. Rows carry no check.

Value: empty

Inside a Field#

In a Field, the label names both the field and the list, the hint is wired as its description, and hasError and disabled cascade. Pass name to post the value with the form.

Used to scaffold your project.

Disabled#

disabled on the root locks the whole field. On a row, it stays visible but the keyboard skips it and a click does nothing.

Sizes#

size sets the field height on the shared control scale: sm 32, md 36, lg 40px, level with Input, Select and Button. Typed text stays 14px at every size.

Keyboard#

Focus never leaves the field. Arrow Down and Arrow Up open the list and move the highlight; Home and End jump to the ends while it is open. Enter picks the highlighted row. Escape first clears what you typed, then closes the list, then clears the value. Tab closes and moves on; leaving with exactly an option's text picks it, anything else reverts.

API reference#

Combobox

Root. Owns the value, the typed text and the open state. Renders no DOM of its own.

PropTypeDefaultDescription
valuestring-Controlled selected value. "" means nothing is selected.
defaultValuestring""Initial value when uncontrolled.
onValueChange(value: string) => void-Called when an option is picked or the value is cleared.
inputValuestring-Controlled text in the field, e.g. to drive a search request.
defaultInputValuestring-Initial text when uncontrolled.
onInputValueChange(inputValue: string) => void-Called on every keystroke, pick, clear and revert.
itemToString(value: string) => string-Label shown for a value seeded from outside, before the list has rendered.
autoHighlightbooleantrueHighlight the first match while typing, so Enter picks it.
openOnInputClickbooleantrueOpen the list when the field is clicked.
size"sm" | "md" | "lg"-Field height, shared with Input, Select and Button (32, 36, 40px). Defaults from density.
density"comfortable" | "compact"-Row spacing in the list. Also picks the default size.
openboolean-Controlled open state of the list.
defaultOpenbooleanfalseOpen state on mount when uncontrolled.
onOpenChange(open: boolean) => void-Called when the list opens or closes.
filter(query: string, textValue: string, keywords?: string[]) => number | nullcontainsReturn 0 to hide a row. Case and accent blind by default. Pass null to show every row (server results).
namestring-Posts the value with a native form submit.
disabledboolean-Disables the field. Inherited from a surrounding Field.

Autocomplete

Root for free text. The field's text is the value; picking a row fills it in. Takes the same parts.

PropTypeDefaultDescription
valuestring-Controlled text.
defaultValuestring""Initial text when uncontrolled.
onValueChange(value: string) => void-Called on every keystroke and pick.
autoHighlightbooleanfalseOff by default, so Enter keeps what was typed.
openOnInputClickbooleanfalseOpens on typing only, by default.
size"sm" | "md" | "lg"-Field height, shared with Input, Select and Button (32, 36, 40px). Defaults from density.
density"comfortable" | "compact"-Row spacing in the list. Also picks the default size.
openboolean-Controlled open state of the list.
defaultOpenbooleanfalseOpen state on mount when uncontrolled.
onOpenChange(open: boolean) => void-Called when the list opens or closes.
filter(query: string, textValue: string, keywords?: string[]) => number | nullcontainsReturn 0 to hide a row. Case and accent blind by default. Pass null to show every row (server results).
namestring-Posts the value with a native form submit.
disabledboolean-Disables the field. Inherited from a surrounding Field.

ComboboxInput

The canonical Input with role="combobox", a clear button and a chevron. Other props go to the <input>.

PropTypeDefaultDescription
prefixReactNode-A leading glyph: a search icon, the picked option's flag.
showClearbooleantrueShow the clear button while the field holds text.
showTriggerbooleantrue (Combobox), false (Autocomplete)Show the chevron that opens the list.
classNamestring-Styles the field's frame.
inputClassNamestring-Styles the text input.
Data attributeDescription
data-stateopen or closed, on the frame.

ComboboxContent

The floating listbox, anchored to the field and exactly its width. Accepts Radix Popover content props.

PropTypeDefaultDescription
labelstring"Options"Accessible name, when there is no Field label to borrow.
align"start" | "center" | "end""start"Alignment against the field.
sideOffsetnumber6Gap from the field, in px.
Data attributeDescription
data-sidetop or bottom, after collision handling.
CSS variableDescription
--radix-popover-content-available-heightCaps the list height so it scrolls inside the viewport.

ComboboxItem

An option row: Select's anatomy, with the brand check on the right.

PropTypeDefaultDescription
value*string-The value this row commits.
textValuestringrow textWhat the filter matches and what the field shows once picked.
keywordsstring[]-Extra terms that also match (a code, a synonym).
disabledboolean-Skipped by the keyboard and not pickable.
onSelect(value: string) => void-Fires when the row is picked.
Data attributeDescription
data-activePresent on the highlighted row.
data-statechecked on the selected row.
data-disabledPresent when disabled.

ComboboxGroup

A group of rows. Hides itself when every row in it filters out.

ComboboxLabel

A group heading, muted.

ComboboxSeparator

A hairline between groups. Hidden while filtering.

ComboboxEmpty

Shown only while the list has no rows. Defaults to "No results found."

ComboboxStatus

A polite live message in the list: searching, an error, a hint.

PropTypeDefaultDescription
loadingboolean-Lead with the Spinner.

FAQ#

Combobox when the user types to find one option and the field itself is the control. Autocomplete when any text is valid and options only suggest (a search box). SelectSearch when you want a Select trigger that opens a panel with a search field inside it.