Multi Select
import { MultiSelect } from '@absoluteui/components/multi-select';import '@absoluteui/components/multi-select/style.css';Using Angular? See Multi Select for Angular.
What it does
Section titled “What it does”An editable combobox with a multi-selectable listbox (WAI-ARIA 1.2) for choosing several values.
- Selected values appear as chips with remove buttons; typing filters (or loads) options.
- Activating an option toggles it, and the list stays open for further choices unless
closeOnSelectis set. - DOM focus stays on the input; the active option is conveyed through
aria-activedescendant. - The listbox renders in the browser top layer (Popover API) and is positioned by the positioning engine.
When to use
Section titled “When to use”- Choosing several values from a list long enough to benefit from filtering (roughly 7+ options).
- Tags, skills, recipients, or teams from a known (or searchable) set.
When not to use
Section titled “When not to use”- A few options that fit on screen: use Checkbox Group.
- One value: use Combobox or Select.
- Free-text tags that are not in a list (custom values are not supported).
Examples
Section titled “Examples”Several values in a form
Section titled “Several values in a form”import { MultiSelect, type MultiSelectChangeDetail } from '@absoluteui/components/multi-select';
const form = document.createElement('form');const host = document.createElement('div');form.append(host);document.body.append(form);const skills = MultiSelect.create(host, { label: 'Skills', name: 'skills', required: true, selectAll: true, maxSelected: 5, options: [ { value: 'ts', label: 'TypeScript' }, { value: 'sql', label: 'SQL' }, { value: 'ux', label: 'UX research', description: 'Interviews and testing' }, ], value: ['ts'],});skills.host.addEventListener('aui-change', (event) => { const { value, added, removed } = (event as CustomEvent<MultiSelectChangeDetail>).detail; console.log(value, added, removed);});Options loaded while typing
Section titled “Options loaded while typing”import { MultiSelect, type MultiSelectOption } from '@absoluteui/components/multi-select';
const host = document.createElement('div');document.body.append(host);MultiSelect.create(host, { label: 'Reviewers', options: [], loadOptions: async (query, { signal }): Promise<MultiSelectOption[]> => { const response = await fetch(`/api/people?q=${encodeURIComponent(query)}`, { signal }); const people = (await response.json()) as { id: string; name: string }[]; return people.map((p) => ({ value: p.id, label: p.name })); },});import { MultiSelect, type MultiSelectChangeDetail } from '@absoluteui/components/multi-select';import '@absoluteui/components/multi-select/style.css';
const host = document.querySelector<HTMLElement>('#tags');if (host) { MultiSelect.create(host, { label: 'Tags', options: [{ value: 'urgent', label: 'Urgent' }, { value: 'billing', label: 'Billing' }] }); host.addEventListener('aui-change', (e) => { console.log((e as CustomEvent<MultiSelectChangeDetail>).detail.value); });}More usage
Section titled “More usage”import { MultiSelect } from '@absoluteui/components/multi-select';
const host = document.createElement('div');document.body.append(host);MultiSelect.create(host, { label: 'Skills', name: 'skills', selectAll: true, options: [ { value: 'ts', label: 'TypeScript' }, { value: 'sql', label: 'SQL' }, ],});Anatomy
Section titled “Anatomy”<div class="aui-multi-select"> (consumer host) <label class="aui-multi-select__label" for=input> <div class="aui-multi-select__control"> <ul class="aui-multi-select__chips" aria-label="Selected: {label}"> <li class="aui-multi-select__chip"><span class="aui-multi-select__chip-label"> <button class="aui-multi-select__remove"> </ul> <input role="combobox" class="aui-multi-select__input"> <button class="aui-multi-select__toggle" tabindex="-1" aria-label="Show options"> </div> <div class="aui-multi-select__description"> <div class="aui-multi-select__error"> <input type="hidden" name> ... (one per selected value) <div class="aui-multi-select__popup" popover="manual"> <div role="listbox" aria-multiselectable="true" class="aui-multi-select__listbox"> <div role="option" class="aui-multi-select__option aui-multi-select__option--all">Select all</div> <div role="option" class="aui-multi-select__option"><span class="aui-multi-select__check"> ... </div> </div> <div class="aui-multi-select__empty">No results</div> </div></div>API reference
Section titled “API reference”Factory
Section titled “Factory”MultiSelect.create(host: HTMLElement, options: MultiSelectOptions): MultiSelectGlobal configuration key: multiSelect (see Configuration).
Options
Section titled “Options”| Option | Type | Default | Description |
|---|---|---|---|
label |
string |
Visible label and accessible name. Required. | |
options |
readonly MultiSelectOption[] |
{ value, label, disabled?, description? } with unique values. Required. | |
value |
readonly string[] |
[] |
Selected values in selection order; unknown and duplicate values are dropped. |
name |
string |
Form field name: one hidden input per selected value. | |
placeholder |
string |
Input placeholder while nothing is selected (never replaces the label). | |
description |
string |
Help text referenced by aria-describedby. | |
required |
boolean |
false |
At least one value; value-missing after touch or reportValidity(). |
disabled |
boolean |
false |
Disables the input, toggle, and chip remove buttons. |
readOnly |
boolean |
false |
Shows the value; the list does not open and chips have no remove buttons. |
invalid |
boolean |
false |
Consumer-managed invalid state (shown immediately). |
errorMessage |
string |
Message shown when invalid is true (text only). | |
maxSelected |
number |
Most values that can be selected (integer >= 1); unselected options become unavailable once reached, and reaching it is announced. | |
selectAll |
boolean |
false |
A “Select all” option that toggles every enabled option shown (up to maxSelected). |
filter |
'contains' | 'startsWith' | 'none' |
"contains" |
Diacritic- and case-insensitive filtering of options while typing. Globally configurable. |
closeOnSelect |
boolean |
false |
Close the list after each toggle. Globally configurable. |
openOnFocus |
boolean |
false |
Open the list when the input gains focus or is clicked. Globally configurable. |
locale |
string |
"en-US" |
Messages and filtering locale. Globally configurable. Inherited from common configuration. |
direction |
'ltr' | 'rtl' |
"ltr" |
Popup alignment. Globally configurable. Inherited from common configuration. |
renderOption |
(option: MultiSelectOption, context: MultiSelectRenderContext) => Node |
Custom option content; the label stays the accessible name. Nodes are released with releaseNode when dropped. | |
loadOptions |
(query: string, context: MultiSelectLoadContext) => Promise<readonly MultiSelectOption[]> |
Load options for the typed text instead of filtering options; debounced, aborted by a newer query or closing; loading and errors are shown and announced. | |
loadDelay |
number |
250 |
Milliseconds between the last keystroke and loadOptions (0 to 5000). |
icons |
Partial<{ expand: IconRef; selected: IconRef; remove: IconRef; error: IconRef }> |
Icon slot overrides. |
Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
host |
HTMLElement |
Consumer-provided host (children are owned by the component). |
input |
HTMLInputElement |
The combobox input. |
listbox |
HTMLDivElement |
The multi-selectable listbox. |
value |
string[] |
Selected values in selection order (a copy). |
selectedOptions |
MultiSelectOption[] |
Options of the selected values. |
isOpen |
boolean |
Whether the listbox is shown. |
name |
string | null |
Form field name. |
label |
string |
Accessible label. |
disabled |
boolean |
Disabled state. |
validity |
FieldValidity |
{ valid, error, message }. |
dirty |
boolean |
The value differs from the initial value. |
touched |
boolean |
Focus has left the control. |
Methods
Section titled “Methods”| Method | Description |
|---|---|
setValue(value: readonly string[]): void |
Programmatic selection (no aui-change); unknown values are dropped. |
update(options: Partial<MultiSelectOptions>): void |
Merge and apply options. |
open(): void |
Show the listbox. |
close(): void |
Hide the listbox. |
focus(): void |
Focus the input. |
getFormValue(): string[] |
The selected values. |
reportValidity(): boolean |
Show messages now and return validity. |
setCustomError(message: string | null): void |
External error; null clears it. |
reset(): void |
Restore the initial value and clear touched/submitted state (no aui-change). |
destroy(): void |
Remove the component, listeners, and released nodes. |
Events
Section titled “Events”| Event | Detail | Bubbles | Cancelable | Description |
|---|---|---|---|---|
aui-change |
{ value: string[]; added: string[]; removed: string[] } |
Yes | No | The user changed the selection (option, Select all, chip remove, or Backspace). |
aui-input |
{ query: string } |
Yes | No | The user edited the input text. |
aui-open |
null |
No | No | The listbox opened. |
aui-close |
null |
No | No | The listbox closed. |
CSS classes
Section titled “CSS classes”All rules live in the aui.components cascade layer, so application styles override them without specificity battles.
| Class | Purpose |
|---|---|
aui-multi-select |
Host. |
aui-multi-select__control |
The input-like boundary around chips, input, and toggle. |
aui-multi-select__chip | __chip-label | __remove |
Selected value chip, its label, and its remove button. |
aui-multi-select__input | __toggle |
Combobox input and list toggle. |
aui-multi-select__popup | __listbox | __option | __option--active | __option--all | __check |
Top-layer popup, listbox, options, active option, Select all, and selection mark. |
aui-multi-select--invalid | --disabled |
State modifiers on the host. |
Icon slots
Section titled “Icon slots”| Slot | Default icon |
|---|---|
expand |
chevron-down |
selected |
check |
remove |
close |
error |
alert-circle |
Design tokens
Section titled “Design tokens”Tokens this stylesheet reads (verified in CI). Override them in a theme or with the Theme Designer.
--aui-border-width | --aui-color-border | --aui-color-border-strong | --aui-color-disabled-bg | --aui-color-error-fg | --aui-color-focus-ring | --aui-color-hover | --aui-color-selected | --aui-color-selected-indicator | --aui-color-selected-text | --aui-color-surface | --aui-color-surface-sunken | --aui-color-text | --aui-color-text-disabled | --aui-color-text-muted | --aui-control-height-md | --aui-control-height-sm | --aui-control-padding-x | --aui-duration-fast | --aui-easing-standard | --aui-focus-ring-offset | --aui-focus-ring-width | --aui-font-family-sans | --aui-font-size-md | --aui-font-size-sm | --aui-font-weight-medium | --aui-icon-size-sm | --aui-line-height-normal | --aui-line-height-tight | --aui-popover-max-height | --aui-radius-md | --aui-radius-sm | --aui-shadow-md | --aui-space-1 | --aui-space-2 | --aui-space-3 | --aui-touch-target-min
TypeScript definitions
Section titled “TypeScript definitions”interface MultiSelectOption { readonly value: string; readonly label: string; readonly disabled?: boolean; readonly description?: string }interface MultiSelectChangeDetail { value: string[]; added: string[]; removed: string[] }type MultiSelectFilter = 'contains' | 'startsWith' | 'none';Accessibility
Section titled “Accessibility”Pattern: WAI-ARIA APG Combobox pattern (editable, list autocomplete, no automatic selection) with a multi-selectable listbox; selected values as a list of chips with remove buttons.
Keyboard
Section titled “Keyboard”| Key | Context | Action |
|---|---|---|
| ArrowDown / ArrowUp | Closed | Opens and activates the first / last enabled option. |
| Alt+ArrowDown | Closed | Opens without activating an option. |
| ArrowDown / ArrowUp | Open | Moves the active option, skipping unavailable options, wrapping. |
| PageDown / PageUp, Ctrl+Home / Ctrl+End | Open | Moves the active option by 10 / to the first or last. |
| Enter | Open with an active option | Toggles it (Select all toggles the options shown); the list stays open unless closeOnSelect. |
| Backspace | Empty input | Removes the last selected value. |
| Escape | Open / closed with text | Closes the listbox / clears the text. |
| Tab | Any | Closes the listbox and moves focus (chip remove buttons are tab stops before the input). |
| Printable characters | Any | Edits the text, opens, and filters (or loads). |
| Element | Role |
|---|---|
| input | combobox |
| listbox container | listbox (aria-multiselectable=true) |
| each option, including Select all | option |
| selected values | list of listitems; each remove control is a native button |
| toggle button | button (native, tabindex=-1) |
ARIA attributes
Section titled “ARIA attributes”| Attribute | Element | Value |
|---|---|---|
aria-expanded |
input and toggle | true when the listbox is shown |
aria-controls |
input and toggle | listbox id |
aria-activedescendant |
input | id of the active option; removed when none |
aria-multiselectable |
listbox | true |
aria-selected |
option | true for selected options; for Select all, when every enabled option shown is selected |
aria-disabled |
option | true for disabled options and, once maxSelected is reached, unselected ones |
aria-label |
chip list and remove buttons | “Selected: {label}” and “Remove {label}” (localized) |
aria-invalid / aria-required / aria-describedby |
input | Reflect invalid, required, description, and error text |
DOM focus stays on the input while choosing; options are never focused. Chip remove buttons take focus with Tab; after removing a value focus returns to the input.
WCAG 2.2 success criteria addressed
Section titled “WCAG 2.2 success criteria addressed”Automated test results: Accessibility and compliance.
Notes and known caveats
Section titled “Notes and known caveats”- Selecting and removing values is announced (“{label} selected”, “{label} removed”) through the shared polite live region, as are result counts, loading errors, and reaching the maximum.
- Selection is shown with a check mark and aria-selected, never by color alone.
- Manual screen-reader verification (NVDA, JAWS, VoiceOver, TalkBack) is pending.
Behavior
Section titled “Behavior”States
Section titled “States”closed, open, filtering, loading, load error, empty results, option active, option selected, option disabled, maximum reached, disabled, read-only, invalid, required.
State transitions
Section titled “State transitions”| From | Trigger | To |
|---|---|---|
| closed | ArrowDown/ArrowUp, typing, toggle click | open |
| open | Enter on an active option / option click | option toggled (aui-change); stays open unless closeOnSelect |
| open | Escape, Tab, outside pointer, blur | closed (the typed text is cleared on blur) |
| any (empty input) | Backspace | last value removed (aui-change) |
| selection below maximum | value reaches maxSelected |
unselected options unavailable; announced |
Accessibility notes
Section titled “Accessibility notes”- No automatic selection: typing never changes the value until the user toggles an option.
- Selections and removals (“{label} selected”, “{label} removed”), result counts, loading errors, and reaching the maximum are announced in the shared polite live region.
- Selection is shown with a check mark and
aria-selected, never by color alone; the active option is outlined (WCAG 1.4.1). - Remove buttons meet the minimum target size (
touch-target-min, WCAG 2.5.8). After removing a value, focus returns to the input. - Verified with axe (WCAG 2.2 AA) with chips and the listbox open in 3 themes; the create budget is checked in
tests/e2e/perf-forms.spec.ts. - Manual screen-reader verification is pending.
Responsive behavior
Section titled “Responsive behavior”The control fills its container and wraps chips onto new lines. The listbox matches the control width, flips above when space below is insufficient, and caps its height to min(popover-max-height, available space).
Right-to-left
Section titled “Right-to-left”Logical properties throughout. Popup alignment follows the direction option or configuration.
Theming
Section titled “Theming”Token-driven. In forced-colors mode, selected options use Highlight/HighlightText, the active outline uses Highlight, and chips and the popup use CanvasText boundaries.
Global configuration
Section titled “Global configuration”configureAbsoluteUI({ components: { multiSelect: { filter: 'startsWith', closeOnSelect: true } } });Component configuration
Section titled “Component configuration”Instance options override global and theme defaults. All configurable options are dynamic.
icons.expand (default chevron-down), icons.selected (default check), icons.remove (default close), and icons.error (default alert-circle). Required fields show the shared visual-only asterisk.
Security
Section titled “Security”Option and chip labels use textContent; loaded options are validated as untrusted data. Positioning writes styles through the CSSOM only, so no style attributes are used. CSP-safe.
Data handling
Section titled “Data handling”No persistence or telemetry. With loadOptions, the consumer performs requests. With name, one hidden input per selected value carries the selection for form submission.
Performance
Section titled “Performance”Filtering is O(n) per keystroke using a normalized comparison: NFD form, diacritics removed, locale lowercase. All filtered options are rendered.
Budget in headless Chromium: create with 1,000 options under 100 ms.
Supported data scale
Section titled “Supported data scale”Tested with 1,000 options. Larger lists should use loadOptions; virtualization is not available.
Edge cases
Section titled “Edge cases”- Unknown and duplicate values in
valueorsetValue()are dropped. - Selected values stay selected, with their last known labels, when
optionsor loaded results change; this keeps chips stable withloadOptions. To drop values, callsetValue()(noaui-change). - Disabled options, and unselected options once
maxSelectedis reached, cannot be activated with the keyboard or clicked. - Select all with a maximum selects options in list order until the maximum is reached.
- Inside a modal dialog, the listbox renders above the dialog, and Escape closes the listbox before the dialog.
Errors
Section titled “Errors”| Code | Category | Cause |
|---|---|---|
MULTI_SELECT_OPTIONS |
configuration | Missing label, non-array options, malformed option, duplicate value, or an invalid option (for example maxSelected below 1). Messages name the property, never the value. |
CFG_INVALID_VALUE / CFG_UNKNOWN_OPTION |
configuration | Invalid or unknown configuration. |
Known limitations
Section titled “Known limitations”- Strict selection only; no custom values. Typed text is cleared when focus leaves.
- No virtualization or option groups.
- Chips cannot be reordered.
- Manual screen-reader verification is pending.
Unsupported scenarios
Section titled “Unsupported scenarios”- Non-string option values.
- Duplicate option values.
- Free-text tags.