Components
MultiSelect
A multi-selection list with checkboxes, groups, and a submit row
A multi-selection list with checkbox indicators, keyboard navigation, group headers, a submit row, and a submitted state.
MultiSelect
Run demo
bunx @gridland/demo multi-selectcurl -fsSL https://raw.githubusercontent.com/thoughtfulllc/gridland/main/scripts/run-demo.sh | bash -s multi-selectInstallation
bunx shadcn@latest add @gridland/multi-selectUsage
import { MultiSelect } from "@/components/ui/multi-select"
import { useKeyboard } from "@gridland/utils"<MultiSelect
items={[
{ label: "TypeScript", value: "ts" },
{ label: "JavaScript", value: "js" },
{ label: "Python", value: "py" },
]}
title="Select languages"
useKeyboard={useKeyboard}
onSubmit={(values) => console.log("Selected:", values)}
/>Examples
Controlled
Use selected and onChange to control the selection externally.
const [selected, setSelected] = useState<string[]>(["ts"])
<MultiSelect
items={[
{ label: "TypeScript", value: "ts" },
{ label: "JavaScript", value: "js" },
{ label: "Python", value: "py" },
]}
selected={selected}
onChange={setSelected}
title="Select languages"
useKeyboard={useKeyboard}
onSubmit={(values) => console.log("Selected:", values)}
/>Default Selected
Set the initially selected items in uncontrolled mode.
<MultiSelect
items={[
{ label: "TypeScript", value: "ts" },
{ label: "JavaScript", value: "js" },
{ label: "Python", value: "py" },
]}
defaultSelected={["ts"]}
title="Select languages"
useKeyboard={useKeyboard}
onSubmit={(values) => console.log("Selected:", values)}
/>Groups
Use the group field on items to render group headers with separators.
<MultiSelect
items={[
{ label: "TypeScript", value: "ts", group: "Languages" },
{ label: "Python", value: "py", group: "Languages" },
{ label: "React", value: "react", group: "Frameworks" },
{ label: "Vue", value: "vue", group: "Frameworks" },
]}
title="Select tools"
useKeyboard={useKeyboard}
onSubmit={(values) => console.log(values)}
/>Disabled Items
Disable individual items so they cannot be toggled.
<MultiSelect
items={[
{ label: "TypeScript", value: "ts" },
{ label: "JavaScript", value: "js", disabled: true },
{ label: "Python", value: "py" },
]}
title="Select languages"
useKeyboard={useKeyboard}
onSubmit={(values) => console.log(values)}
/>Max Selection
Limit how many items can be selected. A counter is shown in the title.
<MultiSelect
items={[
{ label: "TypeScript", value: "ts" },
{ label: "JavaScript", value: "js" },
{ label: "Python", value: "py" },
{ label: "Rust", value: "rs" },
]}
maxCount={2}
title="Pick up to 2"
useKeyboard={useKeyboard}
onSubmit={(values) => console.log(values)}
/>Required
Show a required indicator and use invalid to display an error state.
<MultiSelect
items={[
{ label: "TypeScript", value: "ts" },
{ label: "JavaScript", value: "js" },
{ label: "Python", value: "py" },
]}
required
invalid={selected.length === 0}
title="Select languages"
useKeyboard={useKeyboard}
onSubmit={(values) => console.log(values)}
/>Allow Empty
Show the Submit row even when nothing is selected.
<MultiSelect
items={[
{ label: "TypeScript", value: "ts" },
{ label: "JavaScript", value: "js" },
]}
allowEmpty
title="Select languages (optional)"
useKeyboard={useKeyboard}
onSubmit={(values) => console.log(values)}
/>Disabled
Disable the entire component. Navigation and selection are blocked.
<MultiSelect
items={[
{ label: "TypeScript", value: "ts" },
{ label: "JavaScript", value: "js" },
]}
disabled
title="Select languages"
useKeyboard={useKeyboard}
/>Controls
- ↑/↓ or j/k: Navigate items
- Enter: Toggle item selection (or submit when on the Submit row)
- a: Select all
- x: Clear all
API Reference
MultiSelect
| Prop | Type | Default | Description |
|---|---|---|---|
items | MultiSelectItem<V>[] | [] | Array of selectable items |
defaultSelected | V[] | [] | Initially selected values (uncontrolled) |
selected | V[] | - | Selected values (controlled) |
onChange | (values: V[]) => void | - | Called when the selection changes |
disabled | boolean | false | Disable the entire component |
invalid | boolean | false | Show error state with destructive styling |
required | boolean | false | Show required indicator (*) next to title |
placeholder | string | - | Placeholder text when items list is empty |
maxCount | number | - | Maximum number of selectable items |
title | string | "Select" | Title shown next to the diamond indicator |
submittedStatus | string | "submitted" | Status text shown after submit |
limit | number | 12 | Max visible rows before scrolling |
enableSelectAll | boolean | true | Enable select all with a key |
enableClear | boolean | true | Enable clear all with x key |
highlightColor | string | theme.primary | Color of the highlighted item |
checkboxColor | string | theme.accent | Color of the selection indicator |
allowEmpty | boolean | false | Show Submit row even when nothing is selected |
onSubmit | (values: V[]) => void | - | Called when the Submit row is selected |
useKeyboard | (handler: (event: any) => void) => void | - | Keyboard hook from @opentui/react |
MultiSelectItem
| Field | Type | Description |
|---|---|---|
label | string | Display text |
value | V | Item value |
key | string? | Optional React key |
group | string? | Group header label |
disabled | boolean? | Disable individual item |