When to use this pattern
Use an accordion when visitors only need some sections, such as frequently asked questions. For a single expandable area the native <details> and <summary> elements are often enough: they work without JavaScript and expose their state themselves. A custom accordion is worth it when you need full control over heading structure and design.
Do not hide essential information such as prices or deadlines in collapsed sections that people easily miss.
Roles, states and properties
- Each header is a
<button>inside a heading (<h3>or similar), so the page outline stays intact for screen reader users. aria-expanded="true"or"false"on the button tells whether the section is open.aria-controlspoints to the id of the content panel.- Collapsed panels use
hidden, so they disappear for screen readers and from the tab order too. - Optional:
role="region"witharia-labelledbypointing at the button, but only for a few panels (up to about six); otherwise there are too many landmarks.
Keyboard interaction
| Key | Effect |
|---|---|
| Tab | Moves to the next accordion header or into the open panel. |
| Enter / Space | Opens or closes the section. |
Code
<h3>
<button type="button" aria-expanded="false"
aria-controls="faq-1" id="faq-1-btn">
How long does an audit take?
</button>
</h3>
<div id="faq-1" role="region"
aria-labelledby="faq-1-btn" hidden>
<p>Usually a few working days.</p>
</div>
<script type="module">
for (const btn of document.querySelectorAll('[aria-controls]')) {
btn.addEventListener('click', () => {
const open = btn.getAttribute('aria-expanded') === 'true';
btn.setAttribute('aria-expanded', String(!open));
document.getElementById(
btn.getAttribute('aria-controls')).hidden = open;
});
}
</script>Common mistakes
- A clickable
<div>instead of a button: not reachable by keyboard, no role, no state. - State shown only by a plus or chevron icon, without
aria-expanded. - Content hidden with
height: 0only: links inside stay in the tab order and are read out. - The header is a heading without a button, or the button sits outside the heading, leaving gaps in the heading list.
Related WCAG criteria
Sources
Are your components built like this?
The free check finds many issues automatically. In the audit a person tests every component with a keyboard and a screen reader.