Accordion
Groups related content into collapsible sections to reduce visual complexity. Use when page content can be meaningfully chunked into distinct sections. Don't use when all sections need to be visible simultaneously.
FigmaDescription
Reveals or hides content sections on demand, allowing users to focus on the information most relevant to them without scrolling through everything at once.
Detail pages, FAQ sections, settings panels, and consultation summaries in both Open Point and Social Point. Commonly used in stakeholder profile sidebars and community engagement response displays.
Government interfaces often present dense information across many categories — regulatory details, submission history, engagement stages. Accordions let teams surface that information progressively rather than overwhelming users with a wall of content.
Anatomy
| Part | Required? | Notes |
|---|---|---|
| Trigger / Header | Required | Clickable row containing the section label and expand/collapse indicator. Must be a focusable, keyboard-operable element. Minimum touch target 44x44px. |
| Indicator icon | Required | Chevron or plus/minus icon that rotates or toggles to signal the open/closed state. Should be decorative (aria-hidden) since state is conveyed via aria-expanded. |
| Content panel | Required | The collapsible region. Hidden via display:none or height:0 when collapsed; fully visible and in the tab order when expanded. |
| Divider | Recommended | Horizontal rule between accordion items. Provides visual separation without adding spacing overhead. Uses --op-color-border-default. |
| Section label | Required | Plain-language title describing the section contents. Should be short enough to read at a glance — aim for under 60 characters. |
| Supporting metadata (badge, count) | Optional | Secondary information displayed inline with the header, such as an item count or status badge. Does not replace the section label. |
Variants
Default
Standard collapsible section with chevron indicator
Most cases — detail pages, FAQ, settings groupings
Flush
No outer border or card wrapper; relies on dividers only
When the accordion is embedded inside an existing card or panel and adding an outer border would create double borders
Bordered
Each item has its own visible border and subtle background
When accordions appear on a non-white background (e.g. a grey page canvas) and need clear affordance boundaries
Multi-expand
Multiple sections can be open simultaneously
Reference content or FAQs where users may need to compare information across sections. Avoid for step-by-step flows where sequence matters.
Single-expand (exclusive)
Opening one section automatically closes the others
Wizard steps, structured data entry, or anywhere only one section is actionable at a time
States
| State | Behaviour |
|---|---|
| Collapsed (default) | Content panel is hidden. Trigger displays the section label and a downward-pointing chevron. aria-expanded='false' on the trigger. |
| Expanded | Content panel is visible and participates in tab order. Chevron rotates 180deg (or switches to minus icon). aria-expanded='true' on the trigger. |
| Focus | 3px focus ring using --op-color-interactive-focus appears around the trigger. Visible on keyboard navigation and pointer focus (unless :focus-visible is scoped). |
| Hover | Trigger background shifts to --op-color-bg-subtle. Cursor changes to pointer. No delay. |
| Disabled | Trigger is non-interactive. Opacity reduced to 40%. aria-disabled='true'. Do not use disabled accordions to hide required information — consider using a loading state or conditional rendering instead. |
| Loading | Content panel shows a skeleton or spinner while async content resolves. Trigger remains interactive. Announce load completion to screen readers via aria-live region if content changes after initial render. |
Usage guidelines
When to use
- Displaying a stakeholder profile with multiple distinct information categories (contact details, engagement history, notes) where users rarely need all sections simultaneously.
- FAQ or help content where users scan headings to find the one relevant answer.
- Consultation submission detail pages where regulatory, timeline, and contact sections can be collapsed by default.
- Settings panels with logically grouped options (notifications, privacy, integrations) that would otherwise produce excessive page length.
When not to use
- When users need to compare content across sections simultaneously — use a table or side-by-side layout instead.
- For critical action flows (e.g. a multi-step form submission) where hiding steps creates confusion about progress — use a stepper component instead.
- When there is only one section — a single accordion is unnecessary complexity; use a heading and body text.
- For navigation — use a sidebar nav or tabs. Accordions are content containers, not navigation patterns.
- When the collapsed content is very short (1–2 lines) — the interaction overhead is not worth it; just show the content.
Do / Don't
Do
Write trigger labels as plain-language nouns or short phrases ('Contact details', 'Submission history') that accurately describe what is inside.
Don't
Use vague labels like 'More information' or 'Details' that don't help users decide whether to expand.
Do
Default the most commonly needed section to open if you know the user's primary task.
Don't
Default all sections to open — that defeats the purpose of the pattern.
Do
Keep content panel contents concise. If a panel contains more than a screenful of content, consider splitting it into separate pages or sub-sections.
Don't
Nest accordions inside accordion panels. This creates orientation problems and is difficult to navigate with a keyboard.
Do
Use consistent indicator icons across all accordion instances on the same page.
Don't
Mix chevron and plus/minus icons on the same page or within the same accordion group.
Layout & Spacing
Trigger padding: --op-space-16 (16px) vertical, --op-space-20 (20px) horizontal Content panel padding: --op-space-8 (8px) top, --op-space-20 (20px) right/bottom/left Gap between accordion items (flush variant): 0 — divider handles separation Gap between accordion items (bordered variant): --op-space-8 (8px) Indicator icon size: 20x20px, margin-left: --op-space-12 (12px) from label Minimum trigger height: 44px (touch target compliance)
Tokens
| Part | Token | Value |
|---|---|---|
| Trigger background (default) | --op-color-bg-primary | White on standard page canvas |
| Trigger background (hover) | --op-color-bg-subtle | Light grey on hover |
| Trigger label text | --op-color-text-primary | High-contrast body text |
| Indicator icon | --op-color-text-secondary | Slightly muted to de-emphasise the affordance vs the label |
| Divider | --op-color-border-default | Between items and below expanded content panels |
| Content panel background | --op-color-bg-primary | Same as trigger to maintain a flat appearance |
| Content panel text | --op-color-text-primary | Standard body copy |
| Focus ring | --op-color-interactive-focus | 3px solid, offset 2px, on trigger element |
| Disabled trigger text | --op-color-text-disabled | 40% opacity overlay acceptable as alternative |
| Bordered variant border | --op-color-border-default | 1px solid, radius --op-radius-md (8px) |
Engineering notes
- The wa-details web component (Web Awesome) is the recommended base. It ships with the correct ARIA pattern (button[aria-expanded] + [role=region]) and handles animation via the CSS custom property --show-duration.
- For multi-expand groups, render multiple independent wa-details elements. For single-expand (exclusive) groups, wire a 'wa-show' event listener on each item to close siblings: document.querySelectorAll('wa-details').forEach(el => el.addEventListener('wa-show', () => siblings.forEach(s => s.open = false))).
- Do not use the HTML /
element directly — browser default styling is inconsistent and the animation cannot be controlled without JavaScript hacks.
- Lazy-load heavy content panels (e.g. data tables, maps) by listening for the 'wa-show' event and fetching content only when the panel first opens.
- Respect prefers-reduced-motion: set --show-duration: 0ms and --hide-duration: 0ms in a @media (prefers-reduced-motion: reduce) block.
- If an accordion item should open based on a URL hash (deep link to a section), read location.hash on page load and programmatically set open on the matching wa-details element.
Keyboard interaction
| Key | Action |
|---|---|
| Tab | Moves focus to the next accordion trigger (or next focusable element outside the accordion). When a panel is expanded, Tab also cycles through interactive elements inside the panel. |
| Shift+Tab | Moves focus to the previous accordion trigger or focusable element. |
| Enter | Toggles the focused trigger open or closed. |
| Space | Toggles the focused trigger open or closed (same as Enter). |
| Home | Moves focus to the first accordion trigger in the group (if group keyboard navigation is implemented). |
| End | Moves focus to the last accordion trigger in the group (if group keyboard navigation is implemented). |
Why it matters
Government services are required to meet WCAG 2.1 AA. Many stakeholders and community members using Open Point and Social Point rely on keyboard navigation or screen readers — including older users, users with motor impairments, and users in low-bandwidth environments using assistive technology. Accordions are a common source of accessibility failures when the trigger is not a real button or when state is conveyed only visually.
Focus
Focus is placed on the trigger button when it receives keyboard or pointer focus. When a panel opens, focus remains on the trigger — do not move focus into the panel automatically. When a panel closes, focus remains on the trigger. All interactive elements inside an expanded panel are reachable via Tab in DOM order. Collapsed panels must remove their content from the tab order (use display:none or visibility:hidden, not opacity:0 or height:0 alone).
ARIA
| Role or attribute | When to use | Example |
|---|---|---|
aria-expanded | Applied to the trigger button. Set to 'true' when the panel is open, 'false' when closed. | <button aria-expanded="true" aria-controls="panel-1">Submission history</button> |
aria-controls | Links the trigger button to its controlled panel by ID. | <button aria-controls="panel-submission-history">Submission history</button> |
id (panel) | Unique ID on the content panel element, referenced by aria-controls on the trigger. | <div id="panel-submission-history" role="region" aria-labelledby="trigger-submission-history"> |
role="region" | Applied to the content panel when the accordion group has more than 6 items. Omit for smaller groups to avoid cluttering the landmark list. | <div role="region" aria-labelledby="trigger-1"> |
aria-labelledby (panel) | Points from the content panel back to its trigger, giving the region an accessible name. | <div role="region" aria-labelledby="trigger-submission-history"> |
aria-disabled | Used on trigger buttons that are intentionally non-interactive. Does not prevent focus — use in addition to visual disabled styling. | <button aria-disabled="true">Billing (not available)</button> |
Contrast
Trigger label text (--op-color-text-primary on --op-color-bg-primary): meets 4.5:1 AA for normal text. Trigger label text on hover (--op-color-text-primary on --op-color-bg-subtle): verify contrast ratio when customising --op-color-bg-subtle; must remain >= 4.5:1. Indicator icon (--op-color-text-secondary): icon is decorative (aria-hidden); contrast requirement does not apply, but should be >= 3:1 against background for users who may still perceive it as meaningful. Focus ring (--op-color-interactive-focus): 3px solid ring with 2px offset must achieve 3:1 contrast against both the trigger background and the surrounding page background per WCAG 2.1 SC 1.4.11. Disabled state: --op-color-text-disabled on --op-color-bg-primary should be presented as truly non-interactive; if contrast falls below 4.5:1, this is acceptable per WCAG exception for disabled components, but ensure the disabled state is clearly communicated by more than colour alone.
Touch targets
The trigger row must have a minimum height of 44px and span the full width of its container. Do not restrict the clickable area to just the label text or the icon. On mobile viewports, verify that the trigger height does not collapse below 44px when the label wraps to two lines.
Things to avoid
- Using colour alone to indicate expanded/collapsed state — always pair with an icon change (chevron rotation or plus/minus swap) and aria-expanded.
- Animating the panel open/close without respecting prefers-reduced-motion.
- Nesting accordions — creates navigation confusion for keyboard and screen reader users.
- Auto-collapsing a panel the user has opened in response to a page event they did not initiate.
- Placing critical error messages or required form fields inside a collapsed panel by default — users may not discover them.