Components

Multi-select

A combobox that holds many chosen values as removable tokens. A flex-wrap .ds-multiselect__control (styled like .ds-input) carries the tokens plus a borderless input; typing filters a bordered .ds-multiselect__list popover, click or Enter toggles an option, Backspace on the empty field drops the last token.

Anatomy

A relative .ds-multiselect wrapper anchors the popover under the control. The control shows a focus ring via :focus-within and holds each chosen value as a .ds-multiselect__token with a trailing ×. In the open .ds-multiselect__list (z-index --ds-z-popover), an option shifts to the alt surface on hover or when .is-active; chosen options carry .is-selected and a check.

Control + list
EUR USD
  • GBP — Pound Sterling
  • SGD — Singapore Dollar
<div class="ds-multiselect">
  <div class="ds-multiselect__control">
    <span class="ds-multiselect__token">EUR<button class="ds-multiselect__remove" aria-label="Remove EUR">×</button></span>
    <span class="ds-multiselect__token">USD<button class="ds-multiselect__remove" aria-label="Remove USD">×</button></span>
    <input class="ds-multiselect__input" type="text" role="combobox"
           aria-expanded="true" aria-controls="ms-list" aria-autocomplete="list">
  </div>
  <ul class="ds-multiselect__list" id="ms-list" role="listbox" aria-multiselectable="true">
    <li class="ds-multiselect__option is-active" role="option" aria-selected="false">GBP — Pound Sterling</li>
    <li class="ds-multiselect__option" role="option" aria-selected="false">SGD — Singapore Dollar</li>
  </ul>
</div>

Selected state

A chosen .ds-multiselect__option carries .is-selected and a trailing check, so toggling stays legible inside the open list.

Checked option
  • EUR — Euro
  • USD — US Dollar
  • GBP — Pound Sterling
  • JPY — Japanese Yen
<ul class="ds-multiselect__list" role="listbox" aria-multiselectable="true">
  <li class="ds-multiselect__option is-selected" role="option" aria-selected="true">EUR — Euro</li>
  <li class="ds-multiselect__option is-selected is-active" role="option" aria-selected="true">USD — US Dollar</li>
  <li class="ds-multiselect__option" role="option" aria-selected="false">GBP — Pound Sterling</li>
  <li class="ds-multiselect__option" role="option" aria-disabled="true">JPY — Japanese Yen</li>
</ul>

Interactive

Type to filter; click an option (or use ArrowUp / ArrowDown then Enter) to toggle it. Each token's × removes it, and Backspace on the empty field drops the last. Escape or an outside-click closes the list. The small inline script below wires this demo (no dependencies).

Token multi-select
<div class="ds-multiselect" data-multiselect>
  <div class="ds-multiselect__control" data-multiselect-control>
    <input class="ds-multiselect__input" type="text" role="combobox"
           placeholder="Add currencies…" aria-expanded="false"
           aria-controls="ms-list" aria-autocomplete="list" data-multiselect-input>
  </div>
  <ul class="ds-multiselect__list" id="ms-list" role="listbox" aria-multiselectable="true" hidden data-multiselect-list>
    <li class="ds-multiselect__option" role="option" data-value="EUR">EUR — Euro</li>
    <li class="ds-multiselect__option" role="option" data-value="USD">USD — US Dollar</li>
  </ul>
</div>

React

The MultiSelect filters options as you type, renders chosen values as removable tokens, manages the active option and open state, and supports controlled (value) or uncontrolled (defaultValue). The value is a string[].

<MultiSelect>
import { MultiSelect } from "@diametral/design-system/react";

<MultiSelect
  placeholder="Add currencies…"
  defaultValue={["EUR", "USD"]}
  options={[
    { value: "EUR", label: "EUR — Euro" },
    { value: "USD", label: "USD — US Dollar" },
    { value: "GBP", label: "GBP — Pound Sterling" },
    { value: "JPY", label: "JPY — Japanese Yen" },
  ]}
  onChange={(values) => setCurrencies(values)}
/>