build69
build69  /  Components  /  Selection & Choice  /  MultiCombobox

MultiCombobox

v1.0.22Updated 20 Oct 2026

Dedicated multi-select combobox: a searchable Popover + Command list whose trigger holds one removable chip per selection plus a "+N more" count once the list exceeds `maxDisplay`. Uses a values/onValuesChange array API rather than Combobox's discriminated single/multiple union, so state stays a plain string[]. Chips are removable by mouse, and Backspace in an empty search field drops the last value.

Preview

Live · 8 stories
Multi SelectOpen ↗
▸Why this is recommendedmulti-selectcomboboxchipstags+5

MultiCombobox — select.

Recommended for+14
  • ✓Picking several projects or sites on a form
  • ✓Tag or label pickers with a fixed vocabulary
  • ✓Assigning multiple owners or reviewers
  • ✓Selecting which columns or fields to show
  • ✓Multi-select filters on a report or dashboard
Tags+6
multi-selectcomboboxchipstagssearchautocompletemultiplemultiselect
Features+4
  • Multi-select with removable chips in the trigger
  • "+N more" overflow count via `maxDisplay`
  • Backspace in the empty search field removes the last value
  • Flat or grouped options with headings and separators
  • Plain string[] value API (values / onValuesChange)
  • Custom trigger via the `trigger` prop
  • Server-side filtering hook via `onSearchChange`
  • Labelled by FormField: id and aria-* land on the role=combobox trigger, which then fills the field column
Not recommended for−2
  • ✕Selecting exactly one value (use Combobox)
  • ✕Options fetched per keystroke with loading and error states (use ComboboxAsyncMulti)
  • ✕Free-text tag entry where users invent values (use TagInput)
  • ✕Small fixed sets of 2-5 options (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: "MultiCombobox" })

Usage

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

<MultiCombobox
  options={projects}
  values={selected}
  onValuesChange={setSelected}
  maxDisplay={2}
  placeholder="Select projects"
/>

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 options; grouped entries render headings with separators

ComboboxOption[] | ComboboxGroup[]yes
values

Selected values (default [])

string[]—
onValuesChange

Fires with the next array on toggle or chip removal

(values: string[]) => void—
maxDisplay

How many chips render inline before collapsing to "+N more" (default 3)

number—
placeholder

Trigger text when nothing is selected (default "Select…")

string—
searchPlaceholder

Placeholder for the filter input (default "Search…")

string—
emptyMessage

Shown when no option matches (default "No results.")

string—
onSearchChange

Called as the user types — use it to fetch/filter options server-side. Also called with an empty string when the popover closes, because the search field is discarded with it, so a host that mirrors the query never keeps a stale one

(search: string) => void—
onOpenChange

Fires on every open and close of the popover, including the programmatic close after a selection

(open: boolean) => void—
trigger

Replace the default trigger button; the forwarded ref stays on the default trigger

React.ReactNode—
disabled

Disable the trigger

boolean—
contentClassName

Class applied to the popover content

string—
scrollbar

How the option list draws its scrollbar once it overflows — forwarded to CommandList. Default 'thin': a visible themed bar, because the height cap is the only thing hiding the remaining options

'thin' | 'hidden' | 'auto'—
listMaxHeight

Cap the option list's height (number = px, string = any CSS length such as '50vh'). Omitted, the list keeps CommandList's 18rem default

number | string—
id

DOM id set on the trigger — a <label htmlFor> (or FormField) names the control through it

string—
aria-label

Accessible name for the trigger when there is no visible label

string—
aria-labelledby

Id(s) of the visible label element(s) that name the trigger

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 field invalid (destructive ring); aria-invalid wins when both are set

boolean—
required

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

boolean—
className

Class applied to the trigger; a width here wins over the default (w-full inside a FormField, the standalone width otherwise)

string—

Related