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

Multi Select

A multi-select combobox that toggles values in and out of a selection array, with inline search and full controlled/uncontrolled API.

pnpm dlx shadcn@latest add @kaui/multi-select
import { MultiSelect } from "@/components/ui/multi-select";
import type { SelectionOption } from "@/hooks/use-filtered-options";
const options: SelectionOption<string>[] = [
{ value: "react", label: "React" },
{ value: "typescript", label: "TypeScript" },
];
<MultiSelect
items={options}
value={selected}
onValueChange={setSelected}
placeholder="Select skills..."
/>;

Each option is { value: T, label: string, disabled?: boolean }. The generic T extends string so values stay fully typed. value is always the complete current selection array — not a delta.

Use endAddon to show a live count of selected items alongside a clear-all button.

Wire onQueryChange to a debounced fetch, set disableLocalFilter, and toggle isLoading during the request. The component renders a skeleton in place of the list until results land.

renderOption gives full control over each list item. Use it to add colored indicators, avatars, or secondary metadata. The selected pills below are built from the same data — no separate state needed.

Use emptyContent with a controlled query to surface an inline “Add” action when nothing matches. onMouseDown on the button prevents the input blur that would otherwise close the dropdown before the click fires.

PropTypeDefaultDescription
valueT[]Required. Controlled array of currently selected values
onValueChange(value: T[]) => voidRequired. Called with the full updated array after each toggle
itemsSelectionOption<T>[]Required. Full list of options to display
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
clearQueryOnSelectbooleantrueClear the search query after each selection toggle
closeAfterSelectbooleanfalseClose the dropdown immediately after each selection toggle
disableLocalFilterbooleanfalseSkip 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
isLoadingbooleanShow 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)
endAddonReactNodeTrailing addon rendered inside the input (e.g. a count badge or clear button)

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