Multi Select
Primitive

Multi Select

Headless compact multi-value selection primitives.

Live behavior

Multi Select in motion

Interactive
Atom behavior · App-owned appearance
Preparing behavior…

Interact with the specimen and inspect the behavior Atom contributes.

waiting for input

When to use

Use MultiSelect when users choose several values from a predefined moderate collection and the options should stay collapsed until requested. Use CheckboxGroup for a short visible choice set, Listbox when the collection should remain visible, Select for one value, and Combobox when editable filtering is required.

Features

The behavior Atom owns before your product adds appearance.

  • Coordinates controlled and uncontrolled multi-value selection while keeping the popup open as options are toggled.
  • Provides listbox semantics, roving option focus, typeahead, keyboard selection, focus restoration, and completed outside-interaction dismissal.
  • Supports Field and Form relationships, native multiple-select participation, required validation, reset behavior, and disabled, readonly, and invalid states.
  • Includes positioned popup, portal, viewport, group, label, separator, indicator, scroll-button, and arrow anatomy.

Import

tsx
import { MultiSelect } from "@flowstack-ui/atom/multi-select";

Anatomy

tsx
<MultiSelect.Root defaultValue={["design"]} name="skills">
  <MultiSelect.Trigger>
    <MultiSelect.Value placeholder="Choose skills" />
    <MultiSelect.Icon />
  </MultiSelect.Trigger>
  <MultiSelect.Content>
    <MultiSelect.ScrollUpButton />
    <MultiSelect.Viewport>
      <MultiSelect.Group>
        <MultiSelect.Label>Skills</MultiSelect.Label>
        <MultiSelect.Item value="design">
          <MultiSelect.ItemText>Design</MultiSelect.ItemText>
          <MultiSelect.ItemIndicator />
        </MultiSelect.Item>
      </MultiSelect.Group>
    </MultiSelect.Viewport>
    <MultiSelect.ScrollDownButton />
    <MultiSelect.Arrow />
  </MultiSelect.Content>
</MultiSelect.Root>

API Reference

Root

PropTypeDefault
childrenReactNoderequired
valuestring[]-
defaultValuestring[][]
onValueChange(value: string[]) => void-
open / defaultOpenbooleanfalse
onOpenChange(open: boolean) => void-
disabled / readOnly / invalid / requiredbooleanField value / false
name / formstring-
validationBehavior"native" | "inline"Field/Form value

Values are deduplicated in first-occurrence order. Item activation toggles one value without closing Content. Uncontrolled value and open state return to their defaults on form reset.

Parts

  • Trigger renders a button and owns aria-expanded, aria-haspopup, aria-controls, Field label/description relationships, state data, native props, ref, asChild, and render.
  • Value renders the placeholder for zero values, one label for one value, or First (+N more) for several. renderValue(values, labels) customizes the summary without changing behavior.
  • Content and its Listbox alias render the positioned popup, portal by default, expose data-side, data-align, and data-positioned, and accept onInteractOutside(event). Calling event.preventDefault() keeps the popup open without cancelling the outside destination activation.
  • Viewport, scroll buttons, and Arrow own the specialized overflow and positioned-popup anatomy.
  • Item requires value, accepts label and disabled, exposes option ARIA, checked/highlighted/disabled data, and stays open after toggling.
  • ItemText, ItemIndicator, Group, Label, and Separator preserve the complete collection anatomy.
  • Portal accepts container and disabled.

Accessibility and keyboard

Trigger is a button that opens a focusable popup listbox; it is deliberately not role="combobox" because the APG combobox value model is single-select. Content has role="listbox" and aria-multiselectable="true"; Items have role="option" and aria-selected.

  • Arrow keys move the active option without changing selection.
  • Home/End move to the first/last enabled option.
  • Space or Enter toggles the active option and keeps Content open.
  • Printable keys use option labels for typeahead.
  • Escape closes and restores Trigger focus.
  • Tab and completed mouse, touch, pen, or virtual outside activation close the popup; dragged and cancelled pointer sessions do not.

Forms and Field

When named or required, Root renders an accessibility-hidden native multiple select containing every declared Item. It carries the selected values, name, form, disabled, and required state; redirects invalid focus to Trigger; and uses Atom's Field/Form validation and reset infrastructure. Item labels are collected statically so closed values and native options work before the popup mounts.

Data Attributes

  • Trigger and Content expose data-state="open|closed"; Content also exposes resolved data-side, data-align, and data-positioned.
  • Item exposes data-state="checked|unchecked", data-value, and optional data-highlighted and data-disabled.
  • Stateful parts expose data-disabled, data-readonly, and data-invalid where those states apply. Every rendered part exposes an overridable data-slot.

Scope

MultiSelect intentionally excludes editable filtering, arbitrary tags, creation, select-all, range selection, virtualization, async loading, and mandatory chips. Those require separate interaction contracts.

Changelog

Unreleased

0.24.0

  • Added public Agent Knowledge for component selection, required composition, recurring mistakes, and validation.

0.20.0

  • Added preventable Content/Listbox.onInteractOutside and moved outside dismissal to the shared layer-aware completed-activation contract.

0.10.1

  • Move required and read-only ARIA states from the native button trigger to the multiple-selection listbox that supports them.

0.10.0

  • Add the complete compact multi-value selection primitive with button-owned popup listbox semantics, array state, summary rendering, positioning, dismissal, Field/Form participation, validation, reset, and full anatomy.