Multi Select
Multi-value select with tag chips, inline search filtering, and keyboard navigation.
<arc-multi-select> Overview
MultiSelect is a form control that allows users to choose multiple values from a predefined list of options. Selected items appear as removable tag chips inside the control, giving clear visual feedback about what has been chosen. The inline text input doubles as a filter — typing narrows the dropdown to matching options, making it efficient even with large option sets.
Options are provided declaratively using <arc-option> child elements, each with a value and visible label. The dropdown opens on focus and filters in real time as the user types. Items can be toggled on and off by clicking or via keyboard navigation with ArrowUp/ArrowDown and Enter. Pressing Backspace when the text input is empty removes the last selected tag, providing a natural editing flow.
MultiSelect fires an arc-change event whenever the selection changes, with the current value array in the event detail. The component handles outside-click dismissal automatically and exposes CSS parts for control, tag, input, dropdown, and option to support targeted style customisation.
Guidelines
When to use
- Always provide a `label` so the field is accessible to screen readers
- Use a descriptive `placeholder` to hint at expected input, such as "Choose languages..."
- Keep option labels concise so they display well as tags inside the control
- Listen to `arc-change` to react to selection changes and keep external state in sync
- Pre-populate the `value` array when editing existing records to show current selections
When not to use
- Do not use MultiSelect when only a single value is needed — use Select instead
- Do not provide more than ~50 options without also considering server-side filtering via arc-change
- Do not use extremely long option labels — they will overflow the tag chips and the dropdown
- Do not set both `disabled` and a pre-selected `value` without a clear visual explanation of why editing is blocked
- Avoid nesting MultiSelect inside a popover or modal without testing z-index stacking for the dropdown
Features
- Selected values rendered as removable pill-shaped tag chips inside the control area
- Inline type-ahead filtering that narrows the dropdown options in real time
- Full keyboard navigation: ArrowUp/Down to move, Enter to select, Escape to close, Backspace to remove the last tag
- Check marks next to already-selected options in the dropdown for clear state indication
- Declarative options via `<arc-option>` child elements with `value` and `label` attributes
- Automatic outside-click dismissal of the dropdown panel
- Focus glow on the control using the shared `--focus-glow` design token
- "No results found" empty state when the filter query matches no options
Preview
Usage
This component requires JavaScript. No pure HTML/CSS version is available — use the Web Component directly or a framework wrapper.
<arc-multi-select label="Languages" placeholder="Choose...">
<arc-option value="js">JavaScript</arc-option>
<arc-option value="ts">TypeScript</arc-option>
<arc-option value="py">Python</arc-option>
</arc-multi-select> import { MultiSelect, Option } from '@arclux/arc-ui-react';
export default function Example() {
return (
<MultiSelect label="Languages" placeholder="Choose...">
<Option value="js">JavaScript</Option>
<Option value="ts">TypeScript</Option>
<Option value="py">Python</Option>
</MultiSelect>
);
} <script setup>
import { MultiSelect, Option } from '@arclux/arc-ui-vue';
</script>
<template>
<MultiSelect label="Languages" placeholder="Choose...">
<Option value="js">JavaScript</Option>
<Option value="ts">TypeScript</Option>
<Option value="py">Python</Option>
</MultiSelect>
</template> <script>
import { MultiSelect, Option } from '@arclux/arc-ui-svelte';
</script>
<MultiSelect label="Languages" placeholder="Choose...">
<Option value="js">JavaScript</Option>
<Option value="ts">TypeScript</Option>
<Option value="py">Python</Option>
</MultiSelect> import { Component } from '@angular/core';
import { MultiSelect, Option } from '@arclux/arc-ui-angular';
@Component({
imports: [MultiSelect, Option],
template: `
<arc-multi-select label="Languages" placeholder="Choose...">
<arc-option value="js">JavaScript</arc-option>
<arc-option value="ts">TypeScript</arc-option>
<arc-option value="py">Python</arc-option>
</arc-multi-select>
`,
})
export class MyComponent {} import { MultiSelect, Option } from '@arclux/arc-ui-solid';
export default function Example() {
return (
<MultiSelect label="Languages" placeholder="Choose...">
<Option value="js">JavaScript</Option>
<Option value="ts">TypeScript</Option>
<Option value="py">Python</Option>
</MultiSelect>
);
} import { MultiSelect, Option } from '@arclux/arc-ui-preact';
export default function Example() {
return (
<MultiSelect label="Languages" placeholder="Choose...">
<Option value="js">JavaScript</Option>
<Option value="ts">TypeScript</Option>
<Option value="py">Python</Option>
</MultiSelect>
);
} API
-
placeholderstring'' - Hint text shown inside the control when no items are selected and the input is empty.
-
labelstring'' - Visible label rendered above the control in a small uppercase style.
-
namestring'' -
disabledbooleanfalse - Disables the control, preventing interaction and reducing opacity to 50%.
-
valuestring[][] - Array of selected option values. Updated when items are toggled and emitted via
arc-change. -
readonlybooleanfalse - Prevents toggling options or removing chips while the control stays focusable; the dropdown can still be opened for viewing and the values still submit.
-
size'sm' | 'md' | 'lg''md' - Control size.
mdis the default;smandlgscale the control height and padding. -
formAssociatedbooleantrue -
propertiesobject{ // flag(), unlike `disabled`. The exclusion in props.js is specifically // about form-associated *platform* semantics: a `disabled` content // attribute that is merely present makes the element actually disabled // per the HTML spec, and formDisabledCallback assigns the property back, // so no converter can win. Neither of these is platform-mapped — // `required` is enforced by _computeValidity() below and `readonly` by // each component's own interaction handlers — so the stock converter buys // nothing here and costs the usual bug: `required="false"` read as true, // blocking submission of a form the author meant to leave optional. // Finding #48's shape, across all 26 form controls at once. required: flag(false), readonly: flag(false), } - Lit merges static properties up the prototype chain, so every consumer
gets these without declaring them.
requiredparticipates in constraint validation below;readonlyreflects for styling and is enforced by each component's interaction handlers (the mixin can't know which gestures mutate state). -
autoValidatesbooleantrue - Components that run their own constraint-validation logic (pattern checks, range checks) opt out of the automatic required sync by overriding this to false, and own the whole validity flag set instead.
-
form -
validity -
validationMessage -
requiredbooleanfalse
Methods
-
checkValidity()boolean - Whether the control currently satisfies its constraints, per the native
constraint-validation API. Fires
invalidon the element when it does not, and reports nothing to the user. -
reportValidity()boolean - As checkValidity(), but also shows the browser's validation message against the control when it fails.
Events
-
arc-changedetail: { value: string[] } - Fired when the selected values change
-
arc-inputdetail: { value: string } - Fired on every keystroke in the filter input.
event.detail.valuecontains the current query text.
See Also
- Select Dropdown select with searchable options, keyboard navigation, and full ARIA listbox semantics for accessible form inputs.
- Combobox Searchable dropdown with type-ahead filtering.
- Chip A toggleable pill-shaped element for filters, tags, or multi-select options, with a selected state highlighted in accent-primary.
- Tag Compact pill-shaped label with color variants, custom color support, and an optional remove button, for categorisation, filtering, and selection feedback.