# Button Multi Select (/docs/button-multi-select)





`ButtonMultiSelect` works like [`ButtonSelect`](/docs/button-select), but each item toggles independently and the value is an array. It covers text-formatting toggles, weekday pickers and filter rows. It has the same items, variants, sizes, colors and `Field` integration as `ButtonSelect`; only the value changes shape.

```tsx
import { ButtonMultiSelect } from "@42-central/ui-react/button-multi-select";

<ButtonMultiSelect data={["Mon", "Tue", "Wed"]} defaultValue={["Mon"]} />;
```

## Playground [#playground]

<story.Playground />

## Items [#items]

`data` takes the same items as [`ButtonSelect`](/docs/button-select#items): bare strings, or `{ value, label?, startSlot?, endSlot?, disabled?, color?, "aria-label"?, payload? }` objects. Each item can have its own `color`, and any item can be disabled.

<story.Preview variant="Per-item color" />

## Variants [#variants]

`variant` styles the unselected items (default `light`) and `selectedVariant` the selected ones (default `filled`). Both accept any [`Button`](/docs/button#variants) variant.

<story.Preview variant="Subtle / light" />

## Controlled [#controlled]

`value` is an array of your data's literal values, and `onChange` passes the next array.

<Preview align="center" code="&#x22;const [days, setDays] = useState(['Mon', 'Wed', 'Fri'])\n\n<ButtonMultiSelect data={DAYS} value={days} onChange={setDays} color=\&#x22;brand\&#x22; size=\&#x22;sm\&#x22; />&#x22;">
  <MultiControlledDemo />
</Preview>

## Custom rendering [#custom-rendering]

`renderItem` works as it does on `ButtonSelect`: it receives the typed item and its live `{ selected, disabled }` state.

<story.Preview variant="Custom rendering" />

## Orientation [#orientation]

<story.Preview variant="Vertical" />

## Label, description & error [#label-description--error]

<story.Preview variant="With label" />

<story.Preview variant="Error" />

## Composition [#composition]

Build the group from `ButtonMultiSelect.Root` and `ButtonMultiSelect.Item` when `data` isn't enough. Root sets `variant`, `selectedVariant`, `size` and `color` for every item. Each item renders a `Button` and takes its props. With `asChild`, it renders your own control instead: here an [`ActionIcon`](/docs/action-icon) per formatting toggle. The child receives the selection-driven `variant` and the group `size` (its own `size` wins). Icon-only items need an `aria-label`.

<Preview align="center" code="&#x22;<ButtonMultiSelect.Root aria-label=\&#x22;Formatting\&#x22; defaultValue={['bold']} variant=\&#x22;subtle\&#x22; selectedVariant=\&#x22;light\&#x22; size=\&#x22;sm\&#x22;>\n  <ButtonMultiSelect.Item value=\&#x22;bold\&#x22; asChild>\n    <ActionIcon aria-label=\&#x22;Bold\&#x22;><Bold /></ActionIcon>\n  </ButtonMultiSelect.Item>\n  <ButtonMultiSelect.Item value=\&#x22;italic\&#x22; asChild>\n    <ActionIcon aria-label=\&#x22;Italic\&#x22;><Italic /></ActionIcon>\n  </ButtonMultiSelect.Item>\n  <ButtonMultiSelect.Item value=\&#x22;underline\&#x22; asChild>\n    <ActionIcon aria-label=\&#x22;Underline\&#x22;><Underline /></ActionIcon>\n  </ButtonMultiSelect.Item>\n  <ButtonMultiSelect.Item value=\&#x22;strike\&#x22; asChild>\n    <ActionIcon aria-label=\&#x22;Strikethrough\&#x22;><Strikethrough /></ActionIcon>\n  </ButtonMultiSelect.Item>\n</ButtonMultiSelect.Root>&#x22;">
  <FormattingToolbarDemo />
</Preview>

## Forms [#forms]

`name` renders one hidden input per selected value, all with the same name, so `FormData.getAll(name)` returns the selection.

<Preview align="center" code="&#x22;<form action={save}>\n  <ButtonMultiSelect name=\&#x22;days\&#x22; label=\&#x22;Days\&#x22; data={DAYS} defaultValue={['Mon', 'Tue']} />\n</form>&#x22;">
  <FormDemo />
</Preview>

## API [#api]

<TypeTable
  type="{
  data: {
    type: &#x22;string[] | { value, label?, startSlot?, endSlot?, disabled?, color?, 'aria-label'?, payload?, testId? }[]&#x22;,
    description: &#x22;Items. Bare strings (value === label), or objects.&#x22;,
  },
  value: { type: &#x22;string[]&#x22;, description: &#x22;Controlled value.&#x22; },
  defaultValue: { type: &#x22;string[]&#x22;, description: &#x22;Uncontrolled initial value.&#x22; },
  onChange: {
    type: &#x22;(value: string[]) => void&#x22;,
    description: &#x22;Fires with every selected value, narrowed to the data's value union.&#x22;,
  },
  renderItem: {
    type: &#x22;(item, state) => ReactNode&#x22;,
    description:
      'Render each item\'s content. state is { selected, disabled }. Define it in a &#x22;use client&#x22; module.',
  },
  variant: {
    type: '&#x22;filled&#x22; | &#x22;light&#x22; | &#x22;outline&#x22; | &#x22;subtle&#x22;',
    default: '&#x22;light&#x22;',
    description: &#x22;Button variant of the unselected items.&#x22;,
  },
  selectedVariant: {
    type: '&#x22;filled&#x22; | &#x22;light&#x22; | &#x22;outline&#x22; | &#x22;subtle&#x22;',
    default: '&#x22;filled&#x22;',
    description: &#x22;Button variant of the selected items.&#x22;,
  },
  size: { type: '&#x22;xs&#x22; | &#x22;sm&#x22; | &#x22;md&#x22; | &#x22;lg&#x22; | &#x22;xl&#x22;', default: '&#x22;md&#x22;', description: &#x22;Button size of every item.&#x22; },
  color: { type: &#x22;Color&#x22;, description: &#x22;Palette of every item; an item's own color overrides it.&#x22; },
  orientation: {
    type: '&#x22;horizontal&#x22; | &#x22;vertical&#x22;',
    default: '&#x22;horizontal&#x22;',
    description: &#x22;Lay the items in a row or a column.&#x22;,
  },
  invalid: { type: &#x22;boolean&#x22;, default: &#x22;false&#x22;, description: &#x22;Red invalid state, OR'd with error.&#x22; },
  disabled: { type: &#x22;boolean&#x22;, default: &#x22;false&#x22;, description: &#x22;Disable every item.&#x22; },
  label: { type: &#x22;ReactNode&#x22;, description: &#x22;Field label.&#x22; },
  description: { type: &#x22;ReactNode&#x22;, description: &#x22;Field helper text.&#x22; },
  error: { type: &#x22;ReactNode&#x22;, description: &#x22;Field error; sets the invalid state.&#x22; },
  required: { type: &#x22;boolean&#x22;, default: &#x22;false&#x22;, description: &#x22;Required flag + asterisk.&#x22; },
  name: { type: &#x22;string&#x22;, description: &#x22;Name of the hidden inputs, one per selected value.&#x22; },
  classNames: { type: &#x22;{ field?, label?, root?, item? }&#x22;, description: &#x22;Per-part class overrides.&#x22; },
  testId: { type: &#x22;string&#x22;, description: &#x22;data-testid on the group root.&#x22; },
}"
/>

## Accessibility [#accessibility]

* Built on Ark UI's ToggleGroup machine in multiple mode: the group is a `role="group"` of toggle buttons with `aria-pressed`.
* Roving focus: `Tab` enters and leaves the group. Arrow keys and `Home` / `End` move focus, and `Space` / `Enter` toggles the focused item.
* The label, description and error are wired to the group through the built-in `Field`. Icon-only items need an `aria-label`.
* In a right-to-left locale (from [`IntlProvider`](/docs/intl)), the joined corners mirror and `←` / `→` follow the visual order.
