Skip to content
EspañolEnglish
All components (204)

Multiple select

Choice · MultiSelect · .amset-multiple

What it is and when to use it

Choose several values from a list: the categories of a product, the recipients of a notice, some tags. What is chosen stays in view as chips that can be removed with the mouse or the keyboard, and the field filters the list as you type.

When to use it

  • Several options from a list too long to show in full.
  • max limits how many can be chosen and shows "2 of 4" below the field.
  • allowsCreate for free-form tags: what is typed appears at the end of the list as one more option, Añadir «broches», which works the same with keyboard, mouse or finger.
  • serverFiltered when the list arrives already filtered from a search; onType reports every letter, and loading marks the field as busy while the options arrive.
  • size and variant are the same as for all fields (the box is 30, 36 or 44 px tall and the chips grow with it). readOnly shows what is chosen without remove buttons; disabled turns everything off and takes it out of the Tab order.

When not to

  • A single option: Select, or SearchField if you need to search.
  • Few options that are better seen all at once: Checkbox or SelectableList with selection="multiple".
  • Distributing a catalog while also seeing what is left out: TransferList.

Keyboard: in the field, typing filters without regard to accents or case; the down arrow opens the list and the arrow keys highlight an option without taking focus out of the field; Enter adds the highlighted one and the field empties so you can keep adding; Escape closes the list. Backspace with the field empty removes the last chip. The chips are a grid traversed with the arrow keys: Delete or Backspace remove the focused one and focus moves to the next, instead of being lost. In read-only mode the grid can be traversed but nothing is removed; when disabled, neither the chips nor the field receive focus.

Accessibility: with nothing chosen, the field is named after the label. With chips, the label names the grid of chips and the field is renamed "Add to Categories". Each chip has its named remove button, which is heard only once: "Remove Rings", or exactly what removeText says. The hint, the count and the error are read as the description of the field. With the maximum reached, the list says "You have already chosen 4, which is the maximum" instead of staying empty. The focus of the box is the 2 px ring of the fields, and that of each chip is the one from buttons. With a finger, chips and remove buttons are 44 px.

Motion: the chip that is added enters growing slightly; the border of the chip and the background of the remove button change with a short transition, and the button gives way when pressed.

Fine-tuning: the box shares the field variables, --amset-field-*. The chips, --amset-multiple-chip-height (22, 26 or 32 px depending on size), --amset-multiple-chip-font, --amset-multiple-chip-radius, --amset-multiple-chip-background and --amset-multiple-chip-ink; the remove button, --amset-multiple-remove (its side). The list is adjusted with amset-multiple__panel.

What it looks like

Tags
urgent
Urgent
New customer
Pending review

<MultiSelect
  label="Tags"
  options={[
    { key: 'urgent', label: 'Urgent' },
    { key: 'cliente', label: 'New customer' },
  ]}
  initialValue={['urgent']}
/>

Props

Import it with import { MultiSelect } from '@amset/ui'. Required props are marked.

PropTypeWhat it does
labelrequiredstring—
optionsrequiredreadonly ListOption[]—
allowsCreatebooleanAllows creating values that are not in the list (free-form tags).
classNamestringMerged with the library classes; it does not replace them.
disabledbooleanNothing can be typed, the list does not open and chips cannot be removed: everything leaves the Tab order.
errorstring—
hintstring—
initialValuereadonly string[]—
invalidbooleanMarks the field as invalid. The normal thing is to pass `error`, which also explains it.
loadingbooleanThe options are arriving. Marks the field as busy, shows a ring and a `loadingText` row in the list; the options that were already there stay.
loadingTextstringWhat the loading row says. "Loading…" by default.
maxnumberHow many can be chosen at most.
noResultsMessagestring—
offsetnumber—
onChange(keys: readonly string[]) => void—
onType(text: string) => void—
placeholderstring—
placementPlacement—
readOnlybooleanWhat is chosen is shown and the field can be focused, but nothing is added or removed: the chips lose their remove button.
removeText(label: string) => stringThe whole name of the button that removes each chip: "Remove Rings" by default. It is the entire name; nothing is appended to it.
renderOptionOptionRender<ListOption>Paints the inside of each option in the list: an avatar, two lines, a price. The name the screen reader reads is still `label`.
renderTag(option: ListOption) => ReactNodePaints each chip of what is chosen: an avatar with the name, a colour. The chip is still called `label` for the screen reader and its cross is still there; this is only what is seen inside.
requiredboolean—
serverFilteredbooleanThe list already arrives filtered from outside (server-side search).
size"sm" | "md" | "lg"—
valuereadonly string[]Chosen keys. If passed, the state is managed by whoever uses the component.
variantFieldVariant—

It also accepts the attributes of the element it renders: className, style, data-*, aria-* and its events.

CSS variables

These CSS variables are how you adapt it. Declare them on the component's selector, not on :root: the stylesheet redeclares them in the rule itself, and a declaration on the same element always wins.

VariableDefault
--amset-multiple-chip-height1.625rem
--amset-multiple-chip-fontvar(--amset-text-sm)
--amset-multiple-chip-radiusvar(--amset-radius)
--amset-multiple-chip-backgroundvar(--amset-accent-soft)
--amset-multiple-chip-inkvar(--amset-accent-text)
--amset-multiple-remove1.25rem

How to change it

.amset-multiple {
  --amset-multiple-chip-height: ...;
  --amset-multiple-chip-font: ...;
}

In the test bench

Every state and variant, to try one at a time, on its page at ui.amset.tech: