Selector
Allows users to choose from a set of options presented as selectable cards or tiles. Use when options benefit from richer visual presentation than a radio button or dropdown provides. Don't use when there are more than 6–8 options.
FigmaDescription
Presents a small set of mutually exclusive or multi-select options as visually distinct tiles, allowing users to scan and select based on label, icon, or supporting description rather than a plain text list.
Configuration steps, survey question types, consultation method selection, engagement channel pickers, and onboarding setup flows where a visual choice is clearer than a dropdown.
Government workflows often require users to choose between meaningfully different options — such as consultation types or engagement channels — where a brief description or icon significantly reduces decision errors. Selector gives these choices the visual weight they deserve without requiring a full modal or multi-step form.
Anatomy
| Part | Required? | Notes |
|---|---|---|
| Tile container | Required | The outer interactive element. Renders as a div with role="radio" or role="checkbox" depending on selection mode. Carries all border, background, and focus ring styles. |
| Icon or visual mark | Optional | A wa-icon or illustrative mark placed at the top or left of the tile to aid quick scanning. Should reinforce, not replace, the label. |
| Label | Required | Primary text identifying the option. Should be concise — one to four words is ideal. Uses --op-text-sm, semibold weight, --op-color-text-primary. |
| Supporting description | Optional | Secondary text beneath the label providing additional context. Use when the label alone is ambiguous. Uses --op-text-xs, regular weight, --op-color-text-secondary. |
| Selected indicator | Required | Visual treatment — border colour change, background fill, and a checkmark or radio icon — that communicates selected state. Must not rely on colour alone. |
| Group label | Recommended | A visible fieldset legend or heading labelling the entire selector group. Required for accessibility; recommended to always be visible in the UI. |
Variants
Single select
Exactly one option may be active at a time, equivalent to a radio group.
Use when choices are mutually exclusive — for example, selecting a consultation type (Survey, Workshop, Drop-in) or a stakeholder visibility level.
Multi-select
One or more options may be active simultaneously, equivalent to a checkbox group.
Use when users may legitimately need more than one option — for example, choosing which engagement channels to enable (Email, SMS, Online portal).
Icon tile
Tile prominently features a wa-icon above the label for rapid visual scanning.
Use when options map cleanly to recognisable icons and the set is small (3–6 items).
Text tile
Tile contains label and optional description only, no icon.
Use when options are nuanced and descriptive copy is more useful than an icon, or when suitable icons do not exist.
States
| State | Behaviour |
|---|---|
| Default (unselected) | Tile displays with --op-color-bg-primary background and 1px --op-color-border-default border. Label and description use standard text colours. |
| Hover | Background shifts to --op-color-bg-secondary; border colour shifts to --op-color-interactive-hover. Cursor is pointer. |
| Focus | 3px focus ring in --op-color-interactive-focus appears outside the tile border at 2px offset. Focus is visible on keyboard navigation and must not be suppressed. |
| Selected | Border becomes 2px solid --op-color-interactive-default; background may carry a subtle tint. A checkmark icon (single-select: circle-check, multi-select: square-check) appears in --op-color-interactive-default. |
| Selected and focused | Selected visual treatment is maintained and the 3px focus ring is applied simultaneously. Both indicators are visible at the same time. |
| Disabled | Tile is non-interactive. Whole tile has opacity: 0.4. Cursor is not-allowed. aria-disabled='true' is set; tile is removed from tab order (tabindex='-1'). |
| Error (group-level) | A group-level error message appears below the tile set using --op-color-status-error with an accompanying icon. Individual tiles are not individually marked invalid; the fieldset group conveys the error via aria-describedby. |
Usage guidelines
When to use
- Choosing a consultation type when creating a new project (Survey, Workshop, Drop-in session, Online forum).
- Selecting which engagement channels to activate for a project (Email, SMS, Online portal, In-person).
- Picking a stakeholder group visibility setting (Public, Registered stakeholders, Invite only).
- Onboarding steps where a user configures a preference and visual context reduces mistakes.
When not to use
- When there are more than 6–8 options — use a Dropdown instead.
- When options are purely textual with no meaningful visual differentiation — use a Radio group instead.
- When screen space is constrained on mobile and tiles would require horizontal scrolling — use a Dropdown or Radio group instead.
- When the choice is binary (yes/no, on/off) — use a Switch or single Checkbox instead.
Do / Don't
Do
Keep tile labels to four words or fewer so users can scan quickly.
Don't
Don't write paragraph-length labels inside a tile — move long explanations to hint text outside the tile.
Do
Always include a visible group label (fieldset legend) so the purpose of the selector is clear to all users.
Don't
Don't rely on surrounding page context alone to communicate what the selector group is asking.
Do
Pre-select a sensible default when one option is clearly most common, and explain the default in hint text.
Don't
Don't leave all tiles unselected with no guidance when a default would reduce cognitive load.
Do
Display tiles in a consistent grid or row so users can compare options spatially.
Don't
Don't vary tile sizes within a group — inconsistent sizing implies hierarchy that does not exist.
Do
Show a group-level error message with actionable text when validation fails (e.g. 'Select a consultation type to continue').
Don't
Don't use a red border on individual tiles as the sole error indicator — this fails WCAG 1.4.1 (use of colour).
Layout & Spacing
Tile group: display grid; gap --op-space-12 (12px) between tiles. Default grid: repeat(auto-fill, minmax(180px, 1fr)); max 4 columns on desktop. Tile padding: --op-space-16 (16px) on all sides. Icon size: 24px, margin-bottom --op-space-8 (8px) when stacked above label. Label margin-bottom (when description follows): --op-space-4 (4px). Group label margin-bottom: --op-space-12 (12px). Error message margin-top: --op-space-8 (8px). Minimum tile height: 80px (ensures at least 44px touch target with padding).
Tokens
| Part | Token | Value |
|---|---|---|
| Tile container — default background | --op-color-bg-primary | White in light mode; dark surface in dark mode. |
| Tile container — default border | --op-color-border-default | 1px solid. Transitions to interactive-default at 2px on selection. |
| Tile container — hover background | --op-color-bg-secondary | Subtle tint to signal interactivity on hover. |
| Tile container — hover border | --op-color-interactive-hover | Applied on hover before selection. |
| Tile container — selected border | --op-color-interactive-default | 2px solid (green-400). Increased weight reinforces selection. |
| Tile container — focus ring | --op-color-interactive-focus | 3px outline, 2px offset. Always visible on keyboard focus. |
| Tile container — border radius | --op-radius-md | 8px. Consistent with card and input components. |
| Label text | --op-color-text-primary | Semibold weight, --op-text-sm. |
| Description text | --op-color-text-secondary | Regular weight, --op-text-xs. |
| Icon (unselected) | --op-color-text-secondary | Shifts to --op-color-interactive-default when tile is selected. |
| Selected indicator icon | --op-color-interactive-default | Rendered as wa-icon; do not use background colour as sole indicator. |
| Error message text | --op-color-status-error | Group-level only. Always accompanied by an error icon for non-colour indication. |
| Disabled opacity | opacity: 0.4 | Applied to the whole tile. Acceptable under WCAG 1.4.3 for inactive components. |
Engineering notes
- No wa-* web component exists for Selector. Implement using a fieldset/legend wrapper with styled div elements carrying appropriate ARIA roles.
- For single-select, apply role='radiogroup' on the fieldset and role='radio' with aria-checked on each tile. Use the roving tabindex pattern: selected (or first) tile has tabindex='0'; all others have tabindex='-1'. Arrow keys update focus and selection.
- For multi-select, apply role='group' on the fieldset and role='checkbox' with aria-checked on each tile. Every tile retains tabindex='0' so users can Tab through all options.
- Manage aria-checked updates in JavaScript on click and keyboard events. Do not use native radio/checkbox inputs for visual tiles — use hidden inputs if native form submission is required.
- Apply aria-disabled='true' and tabindex='-1' on disabled tiles. Do not use the HTML disabled attribute on non-input elements.
- For group-level validation, set aria-describedby on the fieldset pointing to the error message element. Add role='alert' to the error element so screen readers announce it on appearance.
- Respect prefers-reduced-motion: remove background and border transition animations when this media query is active.
- Ensure tiles meet 44x44px minimum touch target. With --op-space-16 padding and 80px min-height this is met. Verify icon-only tiles explicitly.
Keyboard interaction
| Key | Action |
|---|---|
| Tab | Moves focus into the selector group (to the selected tile, or the first tile if none is selected). Tab again moves focus out of the group entirely. |
| Arrow Right / Arrow Down | Single-select only: moves focus and selection to the next tile in the group (roving tabindex). |
| Arrow Left / Arrow Up | Single-select only: moves focus and selection to the previous tile in the group. |
| Space | Multi-select: toggles the focused tile. Single-select: selects the focused tile. |
| Enter | Activates the focused tile. Equivalent to Space for selection purposes. |
| Escape | No default action within the selector. If inside a dialog, Escape closes the dialog per that component's behaviour. |
Why it matters
Government platforms serve users with a wide range of abilities, including those relying on keyboard-only navigation or screen readers. Selectors that look interactive but do not support keyboard access exclude a legally protected group of users and may breach the Disability Discrimination Act (Australia) or equivalent legislation in other jurisdictions. Correct ARIA roles and keyboard patterns are mandatory.
Focus
Focus ring: 3px solid --op-color-interactive-focus, 2px offset, applied to the tile container. Focus must always be visible — do not suppress outline with outline: none. Use :focus-visible only with a polyfill for older browsers. Single-select groups use a roving tabindex: the selected (or first) tile has tabindex="0"; all others have tabindex="-1". Arrow key navigation updates which tile holds tabindex="0". Multi-select groups give every tile tabindex="0" so users can Tab through and toggle each option independently.
ARIA
| Role or attribute | When to use | Example |
|---|---|---|
role="radiogroup" | Applied to the fieldset or wrapper of a single-select selector group. | <fieldset role="radiogroup" aria-labelledby="selector-group-label"> |
role="radio" | Applied to each tile in a single-select group. | <div role="radio" aria-checked="false" tabindex="-1"> |
role="group" | Applied to the wrapper of a multi-select selector group. | <fieldset role="group" aria-labelledby="channels-label"> |
role="checkbox" | Applied to each tile in a multi-select group. | <div role="checkbox" aria-checked="false" tabindex="0"> |
aria-checked | Reflects selection state. Must be updated via JavaScript on interaction. Values: 'true' or 'false'. | aria-checked="true" |
aria-labelledby | Points the group wrapper to the visible group label (legend or heading ID). | aria-labelledby="consult-type-label" |
aria-describedby | Points the group wrapper to an error message or hint text element. | aria-describedby="consult-type-error" |
aria-disabled | Set to 'true' on individual tiles that are disabled. Do not use the HTML disabled attribute on non-native elements. | aria-disabled="true" |
Contrast
Label text (--op-color-text-primary) on tile background (--op-color-bg-primary): must meet 4.5:1 for --op-text-sm normal weight. Description text (--op-color-text-secondary) on --op-color-bg-primary: must meet 4.5:1; verify in both light and dark modes. Selected border (--op-color-interactive-default / green-400) does not need to meet text contrast ratios, but selected state must be communicated by means other than colour alone. Error message text (--op-color-status-error) on --op-color-bg-primary: must meet 4.5:1. Disabled tiles at 40% opacity will not meet contrast thresholds — this is acceptable under WCAG 1.4.3 exception for inactive UI components, provided aria-disabled is set correctly.
Touch targets
Each tile must have a minimum touch target of 44x44px. With --op-space-16 (16px) padding on all sides and a minimum tile height of 80px, this is met for any tile with a single-line label. Verify icon-only tile variants explicitly. On mobile, consider a full-width stacked layout to further increase tap area.
Things to avoid
- Using colour as the only visual indicator of selected state.
- Suppressing the focus ring with outline: none or outline: 0 without a robust :focus-visible replacement.
- Forgetting to update aria-checked in JavaScript when a tile is selected or deselected.
- Placing a selector group inside another interactive element without verifying Tab and Escape key behaviour is correct.
- Using non-descriptive group labels such as 'Options' — always describe what is being selected.
- Setting different tile widths or heights within the same group, implying a hierarchy that does not exist.