Toggle View
A button that switches between two views, such as list and thumbnail. Use for immediate, reversible view toggles that do not require form submission. Don't use when the toggle controls a persistent system setting.
FigmaDescription
Lets users switch the display format of a content area between two mutually exclusive views — typically list and grid (thumbnail) — without navigating away or submitting a form.
In content listing pages such as stakeholder directories, document libraries, consultation response lists, and community project galleries where multiple display formats are meaningful.
Different users process information differently. Some prefer a compact list for scanning names or dates; others prefer a grid to compare images or cards at a glance. Toggle View gives users immediate control without changing application state or requiring a page reload.
Anatomy
| Part | Required? | Notes |
|---|---|---|
| Button group container | Required | Wraps both toggle buttons as a single logical control. Communicates grouping to assistive technologies via role='group' and an accessible label. |
| List view button | Required | Icon button representing the list/row layout. Carries the active state when list view is selected. |
| Grid view button | Required | Icon button representing the grid/thumbnail layout. Carries the active state when grid view is selected. |
| Button icon | Required | A recognisable icon (e.g. list-bullet, grid-2x2) that communicates the view type without relying on a visible label. |
| Visible label (optional) | Optional | Short text label ('List', 'Grid') displayed alongside the icon for additional clarity in low-density toolbars. Often hidden at small breakpoints. |
| Active indicator | Required | Visual treatment — filled or outlined background — that distinguishes the currently active view button from the inactive one. |
Variants
Icon only
Compact pair of icon buttons with no visible text label
Use in dense toolbars or when space is constrained, such as alongside filter and sort controls on a listing page. Ensure each button has an aria-label.
Icon with label
Icon accompanied by a short visible text label
Use when users may be unfamiliar with the icon convention, or when the toolbar has sufficient horizontal space. Preferred for first-time or low-frequency government users.
States
| State | Behaviour |
|---|---|
| Default (inactive) | The button appears at rest with a transparent or subtle background. Communicates that the view it represents is available but not currently active. |
| Active | The button representing the current view is visually filled or highlighted using --op-color-interactive-default. aria-pressed='true' is set programmatically. |
| Hover (inactive) | On pointer hover, the inactive button background shifts to --op-color-interactive-hover at reduced opacity, signalling interactivity. |
| Focus | A 3px focus ring using --op-color-interactive-focus appears around the focused button. Both buttons in the group are individually focusable. |
| Disabled | Both buttons are disabled when the content area is loading or unavailable. Opacity is reduced to 40%; pointer-events are removed. aria-disabled='true' is set; do not use the HTML disabled attribute on buttons inside a group if you need them to remain focusable for assistive technology. |
Usage guidelines
When to use
- A content listing page supports two meaningfully different display formats (e.g. a stakeholder directory shown as a compact table or a card grid).
- The user's display preference is session-scoped — it resets on next visit without persisting to their profile.
- The view switch is immediate and does not trigger a form submission or navigation event.
- The listing contains enough items (more than six) that the choice of layout is practically useful.
When not to use
- The toggle controls a persistent user preference stored in their profile — use a Settings page or a Switch component instead.
- There are more than two view options — use a segmented control or select menu instead.
- The content area contains only a few items where layout difference is negligible — remove the toggle to reduce noise.
- The toggle changes application mode or navigates the user to a different route — use navigation tabs or a link instead.
Do / Don't
Do
Place the Toggle View control in the toolbar directly above the content area it affects, aligned to the trailing edge.
Don't
Place the control below the content, in a sidebar, or in a page header that is not visually connected to the listing.
Do
Persist the selection for the duration of the session so users are not surprised when they paginate or filter.
Don't
Reset the view to default on every filter or search interaction — this forces users to re-select their preferred layout repeatedly.
Do
Provide a meaningful aria-label on the group element, e.g. aria-label='View format'.
Don't
Leave the button group without an accessible name — screen reader users will not know what the buttons control.
Do
Use established iconography (list lines for list view, grid squares for grid view) that government users will recognise.
Don't
Use abstract or decorative icons that require a legend to interpret.
Layout & Spacing
Button group container: display: inline-flex gap: --op-space-4 (4px) between buttons
Each toggle button: min-width: 44px min-height: 44px padding: --op-space-8 (8px) --op-space-12 (12px) border-radius: --op-radius-sm (4px) on individual buttons border-radius: --op-radius-md (8px) on outer corners of the group container
Icon size: 20x20px Label (if present): --op-text-sm, margin-inline-start: --op-space-8 (8px)
Tokens
| Part | Token | Value |
|---|---|---|
| Active button background | --op-color-interactive-default | Green-400. Applied to the currently selected view button. |
| Active button icon and label | --op-color-text-on-interactive | Ensures contrast against --op-color-interactive-default background. |
| Inactive button background | --op-color-bg-primary | Transparent or surface background at rest. |
| Inactive button icon and label | --op-color-text-secondary | Subdued treatment to distinguish from active state. |
| Hover background (inactive) | --op-color-interactive-hover | Applied at reduced opacity (12%) to the inactive button on hover. |
| Focus ring | --op-color-interactive-focus | 3px solid outline, 2px offset. Applied to each button individually. |
| Container border | --op-color-border-default | 1px border around the group container to delineate it from surrounding toolbar controls. |
| Disabled opacity | N/A | opacity: 0.4 applied to the entire group when disabled. |
Engineering notes
- The two buttons must be wrapped in a div or span with role='group' and an aria-label describing what is being toggled (e.g. aria-label='View format'). Do not use role='toolbar' unless the toggle sits alongside other unrelated controls in a true toolbar.
- Manage active state in JavaScript: on click, set aria-pressed='true' on the clicked button and aria-pressed='false' on the other. Do not rely solely on CSS classes for communicating state to assistive technologies.
- Store the selected view in sessionStorage (key: opds-toggle-view-[context-id]) so the selection survives pagination, filtering, and sorting interactions within the same session. Do not write to localStorage unless you have explicit UX sign-off to persist across sessions.
- When the content area is loading (e.g. after a filter is applied), set aria-disabled='true' on both buttons and apply pointer-events: none. Remove once the content has resolved.
- If the icon-only variant is used, each button requires a descriptive aria-label ('List view', 'Grid view'). Do not use aria-label on the icon element itself.
- This component is classified as legacy. New implementations should be evaluated against whether a more composable pattern (e.g. a segmented control) better fits the context before using this component.
Keyboard interaction
| Key | Action |
|---|---|
| Tab | Moves focus into the button group, then between the two buttons in DOM order. |
| Enter | Activates the focused button, switching to that view. Updates aria-pressed state on both buttons. |
| Space | Activates the focused button, equivalent to Enter for button elements. |
Why it matters
Government platforms serve a wide range of users including people who rely on screen readers, keyboard-only navigation, or switch access devices. A toggle that only communicates active state visually will be invisible to these users, causing confusion about which view is currently active and whether their interaction was registered.
Focus
Both buttons in the group are individually focusable via Tab. The active button does not trap or consume focus. After activating a button, focus remains on that button so the user can switch back immediately if desired. The focus ring (3px, --op-color-interactive-focus) is always visible — it must not be suppressed for mouse users via :focus-not-visible, as government users may mix input methods.
ARIA
| Role or attribute | When to use | Example |
|---|---|---|
role="group" | Applied to the container element wrapping both buttons. | <div role="group" aria-label="View format"> |
aria-label (on group) | Provides an accessible name for the group so screen readers announce context before reading button labels. | aria-label="View format" |
aria-pressed | Applied to each button. Set to 'true' on the active button and 'false' on the inactive one. Updated dynamically on interaction. | <button aria-pressed="true" aria-label="List view">...</button> |
aria-label (on button) | Required when using icon-only variant. Describes the view the button switches to. | aria-label="Grid view" |
aria-disabled | Set to 'true' on both buttons when the content area is in a loading or unavailable state. | aria-disabled="true" |
Contrast
Active button: --op-color-text-on-interactive on --op-color-interactive-default must meet 4.5:1 for text and 3:1 for icon-only (non-text) elements. Inactive button: --op-color-text-secondary on --op-color-bg-primary must meet 4.5:1 minimum. Focus ring: --op-color-interactive-focus must achieve 3:1 against adjacent background colours per WCAG 2.1 SC 1.4.11 (Non-text Contrast). Disabled state: Contrast requirements do not apply to disabled controls under WCAG 2.1, but aim for at least 2:1 to avoid the appearance of broken UI.
Touch targets
Each button must have a minimum interactive tap target of 44x44px. If the visual button is smaller (e.g. icon-only at 32px), use padding or an invisible tap target overlay to meet the minimum. This is critical for government field workers using touch devices in the Open Point mobile web experience.
Things to avoid
- Do not use colour alone to communicate the active state — also use a distinct background fill, border, or weight change so the state is perceivable without colour vision.
- Do not remove the focus ring for any user. Suppressing outlines for mouse/pointer users is a common pattern that harms mixed-input users (e.g. tablet users who switch between touch and keyboard).
- Do not animate the active state transition in a way that causes rapid flashing or large motion. If adding a transition, wrap it in a prefers-reduced-motion media query and fall back to an instant switch.
- Do not use div or span elements as the interactive buttons. Use native button elements to get keyboard and assistive technology support for free.