gridland
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

Terminal
bunx @gridland/demo multi-select
Terminal
curl -fsSL https://raw.githubusercontent.com/thoughtfulllc/gridland/main/scripts/run-demo.sh | bash -s multi-select

Installation

bunx shadcn@latest add @gridland/multi-select

Usage

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.

Controlled
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.

Default selected
<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.

With groups
<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.

Disabled items
<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.

Max selection
<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.

Required with validation
<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.

Allow empty submission
<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.

Disabled
<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

PropTypeDefaultDescription
itemsMultiSelectItem<V>[][]Array of selectable items
defaultSelectedV[][]Initially selected values (uncontrolled)
selectedV[]-Selected values (controlled)
onChange(values: V[]) => void-Called when the selection changes
disabledbooleanfalseDisable the entire component
invalidbooleanfalseShow error state with destructive styling
requiredbooleanfalseShow required indicator (*) next to title
placeholderstring-Placeholder text when items list is empty
maxCountnumber-Maximum number of selectable items
titlestring"Select"Title shown next to the diamond indicator
submittedStatusstring"submitted"Status text shown after submit
limitnumber12Max visible rows before scrolling
enableSelectAllbooleantrueEnable select all with a key
enableClearbooleantrueEnable clear all with x key
highlightColorstringtheme.primaryColor of the highlighted item
checkboxColorstringtheme.accentColor of the selection indicator
allowEmptybooleanfalseShow 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

FieldTypeDescription
labelstringDisplay text
valueVItem value
keystring?Optional React key
groupstring?Group header label
disabledboolean?Disable individual item