Skip to main content

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.

OverviewStyleAccessibility

Description

Anatomy

PartRequired?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

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

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

PartTokenValue
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

KeyAction
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 attributeWhen to useExample
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.

Was this page helpful?

Updated 5 October 2026