Skip to content

An accordion opens without exposing its state

A clickable heading expands a panel, but its role and expanded state are missing.

WCAG 4.1.2 Test with KeyboardScreen readerZoomForced colors Markdown for agents
Broken Opens, but says nothing.
Fixed Announced as expanded.
The same control broken and fixed.
01

The problem

Keyboard and screen-reader users can't tell which headings open, or whether they are open.

What happens A click opens the answer. A key does not. Read moreRead less

An FAQ heading responds to clicks. Its answer appears below, but the heading is plain text in the accessibility tree and keyboard users cannot toggle it.

Why it fails A heading acts as a control, with no state. Read moreRead less

The heading acts as a control without button semantics. The expanded state is not exposed, and hidden panel links may remain in the tab order.

Who is affected Keyboard users, and anyone listening for open or closed. Read moreRead less

A user cannot reliably discover which headings are interactive or whether a panel is open. Invisible tab stops interrupt navigation.

What automation and AI miss The attribute exists. Whether it stays in step is untested. Read moreRead less

Automation can detect some missing attributes, but it cannot establish that aria-expanded matches every state transition or that hidden descendants stop receiving focus. Test repeated opening and closing.

02

Try it

Open the answer with Tab and Enter.

Tab to the question and open it. While closed, keep tabbing: does focus vanish?

A recreated demo. The broken version is intentionally inaccessible.

03

The fix

Use details and summary, or a button inside the heading that reports its state.

  1. 1Keep aria-expanded in step with what is shown.
  2. 2Hide closed panels with hidden, not zero height.
  3. 3Keep the heading level that fits the page outline.
Before A clickable heading and a squeezed panel
<h3 class="faq-q" onclick="toggle(this)">How do refunds work?</h3><div class="faq-a" style="height: 0; overflow: hidden">  <p>Refunds arrive within 5 days. <a href="/refunds">Refund policy</a></p></div>
After A native disclosure
<details>  <summary>How do refunds work?</summary>  <p>Refunds arrive within 5 days. <a href="/refunds">Refund policy</a></p></details>
After A custom accordion

html

<h3>  <button type="button" aria-expanded="false" aria-controls="faq-refunds">    How do refunds work?  </button></h3><div id="faq-refunds" hidden>  <p>Refunds arrive within 5 days. <a href="/refunds">Refund policy</a></p></div>

js

for (const button of document.querySelectorAll('[aria-controls]')) {  button.addEventListener('click', () => {    const open = button.getAttribute('aria-expanded') === 'true';    button.setAttribute('aria-expanded', String(!open));    document.getElementById(button.getAttribute('aria-controls')).hidden = open;  });}
Why this works

The heading cannot be reached with Tab and says nothing about its state. The closed panel is only squeezed to zero height, so its link still takes focus where nobody can see it.

For a simple question and answer, details and summary give you keyboard access, the open and closed state, and hidden content out of the tab order, with no script.

When the design needs more control, put a real button inside the heading and keep its state in step with the panel.

The hidden attribute removes the closed panel from the tab order and from what screen readers can reach. If you animate the panel, make sure it ends with hidden set, not just zero height.

Implementation decisions

Avoid making the entire heading and a separate icon two different toggle controls. A native disclosure usually needs no extra aria-expanded. For custom controls, use stable IDs with aria-controls, and decide whether several panels may stay open. Arrow-key shortcuts are optional; Tab access must remain usable.

Keep the heading level that fits the page outline, so the questions still work as headings for screen-reader navigation.

04

Verify the fix

5 checks, no mouse.

0 of 5 checked

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the shared accordion component changes.

Limits

This is a starting pattern, not a guarantee for every application. The script is a minimal sketch without animation or deep links. Test the complete journey with your target assistive technology.

This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.

References: www.w3.org

Keep this fixed as your code changes.

Turn guides like this into rules your coding agents and CI follow, then have the result retested with a keyboard and screen readers.

How retests work