Multi-Select
Allows users to select multiple options from a searchable dropdown list, displaying selections as removable tags. Use when a checkbox group would be too long or when options need to be searchable. Don't use when only one selection is allowed.
FigmaDescription
Lets users pick one or more options from a searchable dropdown list. Each chosen option appears as a removable tag inside the input field, giving users a clear summary of their selections without navigating away from the form.
Filter panels, form fields that assign categories or tags, bulk-action screens, and search refinement interfaces. Common examples include assigning stakeholder groups to a consultation, filtering engagement records by region or topic, and tagging community submissions.
A checkbox list becomes unwieldy when there are more than five or six options, or when the option set is dynamic. Multi-Select solves this by combining search and selection in a single compact control, reducing scroll and cognitive load for admin users working with large datasets.
Anatomy
| Part | Required? | Notes |
|---|---|---|
| Label | Required | Describes the field. Always visible above the input; never replaced by placeholder text alone. |
| Input trigger | Required | The clickable field that opens the dropdown and accepts typed search queries. Displays selected-value tags and the text cursor. |
| Selected-value tag | Optional | A removable tag rendered inside the input for each chosen option. Includes a close icon so the user can deselect without reopening the list. |
| Placeholder text | Recommended | Short hint shown inside an empty input (e.g. 'Search or select…'). Hidden once tags are present. |
| Dropdown panel | Required | Floating list of options that appears below (or above) the trigger when focused. Contains the search-filtered option items. |
| Search input (internal) | Required | The live-filter text field embedded in the trigger. Filters the option list as the user types. |
| Option item | Required | An individual selectable row in the dropdown. Shows a checkbox or tick indicator for selected state. |
| Select-all option | Optional | A top-of-list item that toggles all visible (filtered) options at once. Useful when bulk selection is a common workflow. |
| Empty state message | Recommended | Shown inside the dropdown when no options match the current search query (e.g. 'No results found'). |
| Helper text | Optional | Supporting instruction or character/count guidance displayed below the input field. |
| Error message | Optional | Replaces helper text when validation fails. Paired with an error icon and --op-color-status-error styling. |
| Clear-all button | Optional | An action inside or adjacent to the input that removes all selected values at once. Provide when selections can grow large. |
| Chevron icon | Recommended | Rotates to indicate open/closed state of the dropdown panel. |
Variants
Default
Standard multi-select with search filter
Use for most multi-selection scenarios where the option list has six or more items or the options are dynamic.
With select-all
Adds a 'Select all' toggle at the top of the dropdown
Use when selecting the full list (or a large portion of it) is a common user intent, such as assigning all regions to a consultation.
Grouped options
Options are organised under labelled group headings within the dropdown
Use when options naturally belong to categories (e.g. stakeholder types grouped by sector) to reduce visual scanning effort.
Fixed options (no search)
Dropdown shows all options with no filter input
Use only when the option set is small (five or fewer) and well-known, so search offers no real benefit.
Read-only
Selections are displayed as non-interactive tags; the input is not editable
Use in summary/review steps of a multi-step form, or in detail views where the field value is shown but cannot be changed in the current context.
States
| State | Behaviour |
|---|---|
| Default (empty) | Input shows placeholder text and chevron. Dropdown is closed. |
| Focused (empty) | Input receives a 3px focus ring using --op-color-interactive-focus. Dropdown opens showing the full unfiltered option list. |
| Typing / filtering | Option list filters in real time as the user types. Non-matching options are hidden. If no options remain, the empty state message appears. |
| Option selected | The chosen option gains a tick/checked indicator in the dropdown. A removable tag for the option appears inside the input field. |
| Multiple selections | Tags stack left-to-right inside the input. The input field grows vertically if tags overflow a single line. The search text cursor remains at the end. |
| Tag removal | Clicking the close icon on a tag deselects that option and removes the tag. The dropdown does not re-open automatically. |
| Dropdown open | Chevron rotates 180°. Dropdown panel appears with --op-radius-md corners and a border using --op-color-border-default. Focus remains inside the composite control. |
| Disabled | Input, tags, and chevron are rendered at reduced opacity. The field is not focusable. Existing selections are visible but cannot be changed. |
| Error | Input border changes to --op-color-status-error. Error message replaces helper text below the input. The field retains full interactivity. |
| Loading | A spinner appears inside the dropdown panel while options are being fetched asynchronously. Keyboard and pointer interactions are suspended until loading completes. |
Usage guidelines
When to use
- The user needs to assign multiple values from a predefined list, such as selecting stakeholder groups for a new consultation.
- The option list has six or more items and would create excessive scroll or visual noise as a checkbox group.
- Options are fetched dynamically or are too numerous to present at once (e.g. all suburbs in a state).
- The user may want to search or type-ahead to find options quickly — for example, finding specific community topics from a long taxonomy.
- Filtering records by multiple criteria simultaneously (e.g. filter engagement activity by region AND topic).
When not to use
- Only one selection is allowed — use a Select or Radio group instead.
- There are five or fewer options and they are all equally likely to be chosen — a Checkbox group is more scannable and requires fewer interactions.
- The user needs to create new values not in the predefined list — use a tag input or combobox pattern with free-text creation instead.
- The field is inside a narrow mobile form where the tag wrapping behaviour may cause layout issues — consider a full-screen selection sheet on small viewports.
Do / Don't
Do
Write the label as a clear noun phrase describing what is being selected, e.g. 'Stakeholder groups' or 'Consultation topics'.
Don't
Do not use the label as an instruction, e.g. 'Select one or more groups'. The component's affordance communicates multi-select.
Do
Provide an empty state message when search returns no results, e.g. 'No topics match your search'.
Don't
Do not leave the dropdown blank when filtering returns nothing — users will not know whether the list is loading or genuinely empty.
Do
Pre-populate selections when editing an existing record so users can see and adjust current values.
Don't
Do not silently clear existing selections when a form is re-opened or navigated back to.
Do
Limit tag labels to 30–40 characters max. Truncate with a tooltip if an option name is longer.
Don't
Do not let tag text wrap inside the tag — this breaks the visual rhythm of the input and can cause layout reflow.
Do
Use the 'Select all' variant when administrators routinely need to assign all options (e.g. grant access to all regions).
Don't
Do not show 'Select all' in filtered/search state if it would only select visible results — clarify the scope in the label.
Layout & Spacing
Input trigger: min-height 44px; padding --op-space-8 (8px) --op-space-12 (12px) Tag inside input: height 28px; padding 0 --op-space-8; gap --op-space-4 between icon and label Gap between tags: --op-space-4 (4px) Dropdown panel: margin-top --op-space-4 (4px) from trigger bottom edge Option item: height 44px min; padding --op-space-8 --op-space-12; gap --op-space-8 between checkbox and label Group heading: padding --op-space-4 --op-space-12; font-size --op-text-xs; font-weight 600 Helper / error text: margin-top --op-space-4; font-size --op-text-sm
Tokens
| Part | Token | Value |
|---|---|---|
| Input trigger border (default) | --op-color-border-default | 1px solid |
| Input trigger border (focused) | --op-color-interactive-focus | 3px solid focus ring outside border |
| Input trigger border (error) | --op-color-status-error | 1px solid; also applied to error message icon |
| Input trigger background | --op-color-bg-primary | White in light mode |
| Placeholder text | --op-color-text-secondary | Subdued; non-essential text per WCAG 1.4.3 exception, but aim for 3:1 minimum |
| Input text / tag label | --op-color-text-primary | Full contrast body text |
| Selected-value tag background | --op-color-interactive-default | green-400; use --op-color-interactive-hover on tag hover |
| Selected-value tag label | --op-color-text-on-interactive | Ensure 4.5:1 contrast against tag background |
| Tag close icon | --op-color-text-on-interactive | Same token as tag label; 44x44px touch target on mobile |
| Dropdown panel background | --op-color-bg-primary | With box-shadow for elevation |
| Dropdown panel border | --op-color-border-default | 1px solid |
| Option item background (hover) | --op-color-interactive-default | At 8% opacity |
| Option item background (selected) | --op-color-interactive-default | At 12% opacity; combined with tick icon |
| Option checkbox (checked) | --op-color-interactive-default | Fill matches interactive green |
| Group heading text | --op-color-text-secondary | Uppercase or semibold at --op-text-xs |
| Helper text | --op-color-text-secondary | --op-text-sm |
| Error message text | --op-color-status-error | --op-text-sm; paired with error icon |
| Disabled opacity | opacity: 0.4 | Applied to entire control; do not apply to label |
| Border radius — input trigger | --op-radius-md | 8px |
| Border radius — dropdown panel | --op-radius-md | 8px |
| Border radius — tag | --op-radius-pill | 999px |
Engineering notes
- Multi-Select is not yet available as a native wa-* Web Awesome component. Implement using a custom element or a library such as Choices.js / Tom Select, ensuring the wa-* visual token layer is applied via CSS custom properties.
- The dropdown panel must be appended to the document body (or a portal container) to avoid clipping inside overflow:hidden ancestors — a common issue in sidebar filter panels.
- Use a debounce of 150–200 ms on the search input handler to avoid excessive async option fetches or filtering cycles.
- When options are loaded asynchronously, set aria-busy='true' on the listbox during loading and remove it once options are populated.
- Persist selection state independently from the visible option list. If a user searches and then clears the query, previously selected options that are no longer visible in the filtered list must still be retained.
- For large option sets (500+ items), implement virtual scrolling in the dropdown panel to avoid DOM performance degradation.
- Emit a standard change event with the current array of selected values whenever selection changes. Include both value and label in the event detail to avoid requiring consumers to re-look-up labels.
- Provide a controlled/uncontrolled API: accept a value prop for controlled usage in React/Angular wrappers and an initial-value attribute for uncontrolled web component usage.
- Ensure the clear-all action does not submit the form — use type='button' on any internal button elements.
Keyboard interaction
| Key | Action |
|---|---|
| Tab | Moves focus into the input trigger. If the dropdown is open, Tab closes it and moves focus to the next focusable element. |
| Enter | Opens the dropdown when the trigger is focused. Selects or deselects the currently highlighted option when the dropdown is open. |
| Space | Same as Enter when focus is on an option item. |
| ArrowDown | Opens the dropdown if closed. Moves highlight to the next option in the list. |
| ArrowUp | Moves highlight to the previous option. When on the first option, moves focus back to the search input. |
| Escape | Closes the dropdown and returns focus to the input trigger. Does not clear the search query. |
| Backspace | When the search input is empty, removes the last selected tag/value. |
| Home / End | Moves highlight to the first or last option in the filtered list. |
Why it matters
Government forms are frequently used by people who rely on keyboard navigation or assistive technologies, including people with motor impairments or those using screen readers in public-facing consultation portals. A multi-select that is not keyboard-accessible creates a hard blocker for these users, potentially constituting a breach of accessibility obligations under the DDA and WCAG 2.1 AA.
Focus
Focus ring: 3px solid --op-color-interactive-focus, offset 2px, visible at all times. When the dropdown opens, focus remains on the search input inside the trigger (not the listbox). Option items are navigated via arrow keys; DOM focus does not move to the listbox element itself. When the dropdown closes (Escape, Tab, or outside click), focus returns to the input trigger. Tag close buttons are focusable via Tab when the dropdown is closed. prefers-reduced-motion: suppress dropdown open/close transitions; keep focus ring visible.
ARIA
| Role or attribute | When to use | Example |
|---|---|---|
role="combobox" | Applied to the input trigger element. | <div role="combobox" aria-haspopup="listbox" aria-expanded="false" aria-controls="multi-select-listbox-id"> |
aria-haspopup="listbox" | Tells screen readers the trigger opens a listbox. | aria-haspopup="listbox" |
aria-expanded | Set to 'true' when the dropdown is open, 'false' when closed. | aria-expanded="true" |
aria-controls | References the id of the listbox element so screen readers can associate the trigger and the list. | aria-controls="stakeholder-groups-listbox" |
role="listbox" | Applied to the dropdown options container. | <ul role="listbox" id="stakeholder-groups-listbox" aria-multiselectable="true"> |
aria-multiselectable="true" | Applied to the listbox element to communicate that multiple selections are permitted. | aria-multiselectable="true" |
role="option" | Applied to each item in the listbox. | <li role="option" aria-selected="false">Community organisations</li> |
aria-selected | Set to 'true' on each option that has been selected; 'false' on unselected options. | aria-selected="true" |
aria-activedescendant | On the combobox/input, references the id of the currently highlighted option to communicate keyboard focus position to screen readers without moving DOM focus. | aria-activedescendant="option-community-orgs" |
aria-label (tag close button) | Each tag's close button must have a descriptive label including the option name. | <button aria-label="Remove Community organisations">x</button> |
aria-live="polite" | A visually hidden live region that announces selection changes (e.g. 'Community organisations added. 3 items selected.') to screen reader users. | <div aria-live="polite" class="sr-only"></div> |
aria-busy | Applied to the listbox during async option loading. | aria-busy="true" |
aria-disabled="true" | Applied to the combobox container when the field is in disabled state. | aria-disabled="true" |
Contrast
Tag background (--op-color-interactive-default / green-400) against tag label (--op-color-text-on-interactive): must meet 4.5:1 for normal-weight text at --op-text-sm. Input text (--op-color-text-primary) against input background (--op-color-bg-primary): must meet 7:1 (AAA) at body size. Placeholder text (--op-color-text-secondary) against --op-color-bg-primary: 3:1 minimum — placeholder is non-essential text per WCAG 1.4.3 exception, but aim higher. Option item text against dropdown panel background: 4.5:1 minimum. Error message text (--op-color-status-error) against --op-color-bg-primary: 4.5:1 minimum; never rely on colour alone — always pair with an icon. Focus ring (--op-color-interactive-focus, 3px): must meet WCAG 2.1 SC 1.4.11 Non-text Contrast (3:1 against adjacent colours).
Touch targets
The input trigger must be at least 44px tall. Each tag's close button must meet 44x44px touch target (use padding to extend the hit area without increasing visual size). Option items must be at least 44px tall in the dropdown. On mobile, consider increasing option item height to 48px for comfort.
Things to avoid
- Do not use colour alone to indicate selected state on option items — always include a tick icon or checkbox.
- Do not auto-close the dropdown after each selection. Government users often need to select several options in sequence; closing after each one is disruptive.
- Do not remove the visible label in favour of placeholder-only design — placeholder text disappears once typing begins and is not reliably announced by all screen readers.
- Do not animate the dropdown open/close with transforms that are not gated behind a prefers-reduced-motion media query.
- Do not rely solely on a count badge (e.g. '3 selected') to communicate selections — always render the actual selected values as tags or a visible list.