build69
build69  /  Components  /  Selection & Choice  /  MultiSelect

MultiSelect

v1.0.15Updated 19 Oct 2026

Token multi-select: popover listbox with checkbox options + built-in search, rendering the selection as removable chips in the trigger. Flat or grouped options, select-all / clear-all that respects the active search filt

Preview

Live · 14 stories
DefaultOpen ↗
▸Why this is recommendedformmulti-selectselectchips+7

The default, out-of-the-box MultiSelect.

Recommended for+14
  • ✓Filter bars (categories, provinces, payment channels)
  • ✓Assignee / staff pickers
  • ✓Tag pickers backed by a known option list
  • ✓Settings panels selecting many of a known set
Tags+6
formmulti-selectselectchipstokenscheckboxsearchlistboxcreatablegroupsdefault
Features+4
  • Controlled (value) or uncontrolled (defaultValue) string[] API
  • Popover listbox with checkbox rows + search filter
  • Selected values as removable chips in the trigger
  • maxDisplayed with "+N more" overflow chip (title shows hidden labels)
  • Grouped options with headings
  • Select-all / deselect-all scoped to the current search filter, plus Clear
  • Optional creatable entries via creatable + onCreateOption
  • Chip tones via the shared tone recipe system
  • Sizes: sm / default / lg
  • Full keyboard: arrows, Enter/Space toggle, Backspace removes last chip, chips focusable with Delete
  • Disabled control and per-option disabled
  • i18n: labels prop overrides every built-in UI string (chips, select-all header, creatable row)
  • Labelled by FormField: the trigger takes the field id and is named by the field label through aria-labelledby
Not recommended for−2
  • ✕Single choice (use Select / Combobox)
  • ✕Free-text-first tagging with paste/reorder (use TagInput)
  • ✕Very small option sets (use Checkbox group)

Install

Ask your agent, or call the MCP tool directly. The resolved bundle is written into your project.

get_registry_item({ name: "MultiSelect" })

Usage

import { MultiSelect } from '@build69/ui';

<MultiSelect
  options={[
    { heading: 'e-Wallet', options: [{ value: 'promptpay', label: 'PromptPay' }] },
    { heading: 'Bank', options: [{ value: 'kbank', label: 'Kasikornbank' }] },
  ]}
  defaultValue={['promptpay']}
  maxDisplayed={3}
  placeholder="Payment channels…"
/>

Tick variants in the left menu — each adds a tab with that variant’s real story source, the same code the preview above renders.

Props

PropTypeRequired
options

Flat or grouped option list

MultiSelectOption[] | MultiSelectGroup[]yes
value

Controlled selected values

string[]—
defaultValue

Initial selection (uncontrolled)

string[]—
onValueChange

Selection change callback

(value: string[]) => void—
maxDisplayed

Chips shown before collapsing into "+N more" (default 3)

number—
listMaxHeight

Cap the option list's height (number = px, string = any CSS length such as '50vh'). Omitted, the list keeps its 16rem (max-h-64) default; either way the list scrolls behind a thin themed scrollbar

number | string—
showSelectAll

Select-all / clear-all header (default true)

boolean—
creatable

Allow creating options from the search query

boolean—
onCreateOption

Map a raw query into a created option

(label: string) => MultiSelectOption—
tone

Chip accent tone (recipe system)

'neutral' | 'primary' | 'success' | 'warning' | 'danger' | 'info'—
size

Trigger + chip sizing

'sm' | 'default' | 'lg'—
labels

Override built-in UI strings (i18n)

Partial<MultiSelectLabels>—
id

DOM id set on the combobox trigger; FormField clones its field id in here

string—
aria-label

Accessible name when there is no visible label

string—
aria-labelledby

Id(s) of the label element(s) that name the trigger — filled in automatically inside a FormField, because a div role=combobox can never be named by <label for>

string—
aria-describedby

Id(s) of hint or error text that describe the trigger

string—
aria-invalid

Mark the trigger invalid — FormField clones this in from its error prop

boolean—
aria-required

Mark the trigger required — FormField clones this in from its required prop

boolean—
invalid

Mark the control invalid (destructive ring + aria-invalid)

boolean—
required

Mark the control required (aria-required on the trigger)

boolean—

Every tonal component shares the same tone × emphasis API, so the pair looks identical across Button, Badge, Alert and more.

Related