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.

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[]) => void—Required. Called with the full updated array after each toggle
itemsSelectionOption<T>[]—Required. Full list of options to display
openboolean—Controlled open state. Omit to let the component manage it
onOpenChange(open: boolean) => void—Called whenever the dropdown opens or closes
querystring—Controlled search query. Omit for uncontrolled
onQueryChange(query: string) => void—Called 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) => boolean—Custom filter predicate. Replaces the default case-insensitive label match
isLoadingboolean—Show 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) => ReactNode—Custom renderer for each list item
startAddonReactNode—Leading addon rendered inside the input (e.g. a search icon)
endAddonReactNode—Trailing 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.