Skip to content
KaUI is under active development. If you run into a bug, please open an issue.

Combobox

A single-select combobox with inline search, async loading support, and full controlled/uncontrolled API.

pnpm dlx shadcn@latest add @kaui/combobox
import { Combobox } from "@/components/ui/combobox";
import type { SelectionOption } from "@/hooks/use-filtered-options";
const options: SelectionOption<string>[] = [
{ value: "react", label: "React" },
{ value: "vue", label: "Vue" },
];
<Combobox
items={options}
selected={selected}
onSelectedChange={setSelected}
placeholder="Search..."
closeAfterSelect
/>;

Each option is { value: T, label: string, disabled?: boolean }. The generic T extends string so you can keep values fully typed.

Use startAddon and endAddon to embed any element inside the input , a search icon, a clear button, a status indicator. The slots are rendered inside InputGroup so they align and interact correctly with the text field.

Wire onQueryChange to a debounced fetch, set disableLocalFilter so the component shows whatever you pass in items, and toggle isLoading while the request is in flight. The skeleton appears in place of the list until results arrive.

Pass renderOption to take full control of how each item looks. Receives the option object and a boolean for selected state , use them to show icons, badges, or secondary text.

Use emptyContent with a controlled query to show an inline “Add” action when no results match. onMouseDown on the button prevents the input from blurring (which would close the dropdown before the click fires).

Pass a ComboboxGroup<T>[] to items to render options under labeled headings. Groups filter independently — a group is removed entirely if none of its items match the query. The flat SelectionOption<T>[] form still works as before.

Pass footerItem to pin a persistent action at the bottom of the list. Unlike emptyContent, it is fully keyboard-navigable and always visible while the dropdown is open — regardless of whether results exist.

The canonical use case is a server-search field where the user can either pick a suggestion or submit the raw typed phrase as a full-text search. Conditionally returning undefined when the query is empty keeps the item hidden until the user has typed something.

PropTypeDefaultDescription
selectedSelectionOption<T> | nullRequired. The currently selected option, or null
onSelectedChange(value: SelectionOption<T> | null) => voidRequired. Called when selection changes; receives null on deselect
itemsSelectionOption<T>[] \| ComboboxGroup<T>[]Required. Flat list of options, or grouped via ComboboxGroup<T> (\{ label?: string; items: SelectionOption<T>[] \})
openbooleanControlled open state. Omit to let the component manage it
onOpenChange(open: boolean) => voidCalled whenever the dropdown opens or closes
querystringControlled search query. Omit for uncontrolled
onQueryChange(query: string) => voidCalled whenever the search input changes
placeholderstring"Search..."Placeholder text shown in the search input
closeAfterSelectbooleanfalseClose the dropdown immediately after an option is selected
disableLocalFilterbooleanSkip client-side filtering. Use when items are already filtered server-side
filterFn(item: SelectionOption<T>, query: string) => booleanCustom filter predicate. Replaces the default case-insensitive label match
isLoadingbooleanfalseShow the loading state in place of the option list
emptyContentReactNode"No results found."Content shown when no options match the query
loadingContentReactNodeSkeleton barContent shown in place of the list while isLoading is true
renderOption(option: SelectionOption<T>, selected: boolean) => ReactNodeCustom renderer for each list item
startAddonReactNodeLeading addon rendered inside the input (e.g. a search icon)
disabledbooleanfalseDisable the entire combobox — input is non-interactive and the dropdown cannot open
clearablebooleanfalseShow a built-in × button when an option is selected. Clears both the selection and the query.
endAddonReactNodeTrailing addon rendered inside the input (e.g. a status indicator). Rendered alongside the clearable button when both are present.
footerItem\{ label: ReactNode; onSelect: () => void \}A persistent action pinned to the bottom of the list. Keyboard-navigable (arrow keys + Enter). Shown whenever the dropdown is open. Pass undefined conditionally to hide it.

All PopoverContent props (align, side, sideOffset, etc.) are also accepted and forwarded to the dropdown.