Saasflare UI
Components

Multi Select

Searchable multi-select with chips, select-all, a max limit, and async option loading — built on Popover + cmdk, with a plain string[] value.

Installation

npx shadcn@latest add https://ui.saasflare.io/r/multi-select.json
pnpm add @saasflare/ui

Usage

Loading…
"use client"import * as React from "react"import { MultiSelect, type MultiSelectOption } from "@saasflare/ui"const FRAMEWORKS: MultiSelectOption[] = [    { value: "react", label: "React" },    { value: "vue", label: "Vue" },    { value: "svelte", label: "Svelte" },    { value: "solid", label: "Solid" },    { value: "angular", label: "Angular" },    { value: "qwik", label: "Qwik" },    { value: "astro", label: "Astro" },]/** * Baseline controlled multi-select: a `string[]` value held in `useState`, * chips on the trigger, search + per-row check indicators out of the box — * the thing you'd otherwise hand-wire from Combobox + Badge + state. */export function Demo() {    const [value, setValue] = React.useState<string[]>(["react", "svelte"])    return (        <div className="max-w-sm">            <MultiSelect                options={FRAMEWORKS}                value={value}                onValueChange={setValue}                placeholder="Pick frameworks…"                searchPlaceholder="Search frameworks…"                aria-label="Frameworks"            />        </div>    )}

Async

Loading…
"use client"import * as React from "react"import { MultiSelect, type MultiSelectOption } from "@saasflare/ui"const DIRECTORY: MultiSelectOption[] = [    { value: "ada", label: "Ada Lovelace" },    { value: "alan", label: "Alan Turing" },    { value: "grace", label: "Grace Hopper" },    { value: "linus", label: "Linus Torvalds" },    { value: "margaret", label: "Margaret Hamilton" },    { value: "dennis", label: "Dennis Ritchie" },    { value: "barbara", label: "Barbara Liskov" },    { value: "ken", label: "Ken Thompson" },]/** * Documented async recipe: `onSearchChange` flips cmdk to `shouldFilter={false}` * so the server is the filter, a debounced fake fetch toggles `loading` (the * spinner row), and the resolved `options` array is what renders. */export function Demo() {    const [value, setValue] = React.useState<string[]>([])    const [options, setOptions] = React.useState<MultiSelectOption[]>(DIRECTORY)    const [loading, setLoading] = React.useState(false)    const timer = React.useRef<ReturnType<typeof setTimeout>>(undefined)    const handleSearch = React.useCallback((query: string) => {        window.clearTimeout(timer.current)        setLoading(true)        timer.current = setTimeout(() => {            const q = query.trim().toLowerCase()            setOptions(                q === ""                    ? DIRECTORY                    : DIRECTORY.filter((o) => o.label.toLowerCase().includes(q)),            )            setLoading(false)        }, 600)    }, [])    React.useEffect(() => () => window.clearTimeout(timer.current), [])    return (        <div className="max-w-sm">            <MultiSelect                options={options}                value={value}                onValueChange={setValue}                loading={loading}                onSearchChange={handleSearch}                placeholder="Assign teammates…"                searchPlaceholder="Search the directory…"                emptyMessage="No teammates found."                aria-label="Teammates"            />        </div>    )}

Select All Max

Loading…
"use client"import * as React from "react"import { MultiSelect, type MultiSelectOption } from "@saasflare/ui"const PERMISSIONS: MultiSelectOption[] = [    { value: "read", label: "Read" },    { value: "write", label: "Write" },    { value: "deploy", label: "Deploy" },    { value: "billing", label: "Billing" },    { value: "admin", label: "Admin" },    { value: "audit", label: "Audit logs" },]/** * `selectAll` header + `max={3}`: over-limit options go non-interactive with a * "Max 3 selected" hint, and `maxRows={1}` collapses extra chips into a * "+N more" badge on the trigger. */export function Demo() {    const [value, setValue] = React.useState<string[]>(["read", "write", "deploy"])    return (        <div className="max-w-sm">            <MultiSelect                options={PERMISSIONS}                value={value}                onValueChange={setValue}                selectAll                max={3}                maxRows={1}                placeholder="Grant permissions…"                searchPlaceholder="Search permissions…"                aria-label="Permissions"            />        </div>    )}

API Reference

Prop

Type

On this page