Accessible accordions
Live demo
Why neurodivergent users benefit from accordions
Long pages with all content visible at once are a significant barrier for ADHD and autistic users. Accordions reduce cognitive load by hiding non-relevant content until the user requests it, letting people focus on one section at a time without the distraction of surrounding text.
For screen reader users, a well-built accordion announces its expanded or collapsed state on every interaction — no guessing whether clicking did anything. The key is using a real <button> element so keyboard and assistive technology support comes for free.
Required WCAG success criteria
- SC 4.1.2 Name, Role, Value (A) — the toggle button must expose its name, its role (button), and its current state (expanded or collapsed) to assistive technology via
aria-expanded. - SC 2.1.1 Keyboard (A) — every accordion header must be reachable and operable by keyboard alone. Enter and Space must toggle the panel; Tab must move to the next interactive element.
- SC 2.4.3 Focus Order (A) — when a panel opens, the focus must remain on the trigger button. Moving focus into the panel on open is not required and often disorienting.
- SC 2.4.7 Focus Visible (AA) — the focused accordion header button must have a clearly visible focus indicator (minimum 3:1 contrast ratio for the focus ring, per SC 2.4.11 AAA).
The correct ARIA pattern
The ARIA Authoring Practices Guide (APG) defines the accordion pattern. The two essential associations are:
- The button carries
aria-expanded(true when open, false when closed) andaria-controlspointing to the panel'sid. - The panel carries
role="region"(only recommended when there are fewer than six panels — too many landmarks is noisy) andaria-labelledbypointing back to the button'sid.
<!-- Accordion item — closed state -->
<div class="accordion-item">
<h3>
<button
id="acc-btn-1"
aria-expanded="false"
aria-controls="acc-panel-1"
class="accordion-trigger"
>
What documents do I need to apply?
</button>
</h3>
<div
id="acc-panel-1"
role="region"
aria-labelledby="acc-btn-1"
hidden
>
<div class="accordion-panel-inner">
<p>You will need a government-issued photo ID and proof of address.</p>
</div>
</div>
</div>
<!-- Accordion item — open state -->
<div class="accordion-item">
<h3>
<button
id="acc-btn-2"
aria-expanded="true"
aria-controls="acc-panel-2"
class="accordion-trigger"
>
How long does the review process take?
</button>
</h3>
<div
id="acc-panel-2"
role="region"
aria-labelledby="acc-btn-2"
>
<div class="accordion-panel-inner">
<p>Reviews are completed within 10 business days.</p>
</div>
</div>
</div>Keyboard interaction
- Enter or Space — toggles the focused accordion panel open or closed.
- Tab — moves focus to the next accordion header (or the next focusable element after all accordions).
- Shift + Tab — moves focus to the previous accordion header or the element before the accordion group.
- Arrow keys (optional) — the APG also defines Up/Down arrows to move between headers without Tab. This is optional for accordions (unlike tab panels) but add it if your users expect it.
Minimal JavaScript toggle
document.querySelectorAll('.accordion-trigger').forEach((btn) => {
btn.addEventListener('click', () => {
const expanded = btn.getAttribute('aria-expanded') === 'true';
const panelId = btn.getAttribute('aria-controls');
const panel = document.getElementById(panelId);
btn.setAttribute('aria-expanded', String(!expanded));
if (expanded) {
panel.setAttribute('hidden', '');
} else {
panel.removeAttribute('hidden');
}
});
});Using the hidden attribute instead of display: none via a CSS class means the browser natively hides the panel from both visual and assistive technology rendering — no extra aria-hidden needed.
Common mistakes
- Using a
<div>as the trigger — a div has no implicit role, no keyboard activation, and noaria-expandedsupport. Screen reader users and keyboard users cannot activate it. Always use<button>. - CSS
display: nonewithout updating ARIA — hiding with CSS alone does not updatearia-expanded. You must set both the visual state and the ARIA attribute together. - No visible focus indicator — removing the default outline without providing a replacement fails SC 2.4.7. Use a custom outline that meets 3:1 contrast.
- Wrapping the entire header in an anchor tag — anchor elements navigate; they should not be used for toggle actions. The trigger must be a
<button>inside the heading, not an<a>. - Forgetting heading structure — each accordion trigger should be wrapped in an appropriate heading element (e.g.,
<h3>) so the section structure is navigable via screen reader heading shortcuts.
Testing checklist
- Tab to each accordion header — confirm focus is visible and button role is announced.
- Press Enter and Space — confirm the panel opens and
aria-expanded="true"is announced. - With a screen reader (NVDA + Firefox, VoiceOver + Safari) — navigate to a closed header and activate it; confirm the screen reader announces "expanded" or "collapsed" on state change.
- Inspect the DOM — confirm hidden panels have
hiddenattribute or equivalent, and thataria-expandedmatches visual state. - Run axe-core — check for
aria-required-childrenandbutton-nameviolations.
