Skip to main content

Notes

Making Accordions and Expandable Sections Accessible: The WAI-ARIA Pattern for E-Commerce

Quietramp ·

Making Accordions and Expandable Sections Accessible: The WAI-ARIA Pattern for E-Commerce

Quick answer: An accordion header needs to be a real button (or an element with role="button"), wrapped in a heading with an aria-level appropriate to the page, with aria-expanded flipping between "true" and "false" as its panel opens and closes, and aria-controls pointing at the panel’s id. That’s the entire WAI-ARIA Authoring Practices Guide (APG) Accordion pattern — no arrow-key navigation required, unlike tabs. A <div> with an onclick handler toggling a CSS class gets none of it: no role, no exposed open/closed state, often no keyboard support at all.

This article covers technical accessibility practice, not legal advice — it explains what WCAG 2.1 AA requires for interactive controls and how to implement it, not whether a specific site carries legal risk.

Where this pattern shows up on an e-commerce site

Accordions are a common show/hide pattern for content that’s useful but would make a page too long if fully visible at once: an FAQ block, a “Shipping & Returns” or “Materials & Care” section on a product page, a “Read more” description truncation, or collapsible groups in a mobile filter panel. This list is Quietramp’s own observation from reviewing e-commerce sites, not something WCAG itself specifies — the spec defines the pattern, not where a store uses it.

The APG’s own definition is precise: “An accordion is a vertically stacked set of interactive headings that each contain a title, content snippet, or thumbnail representing a section of content.” Each heading — the APG calls it an “Accordion Header” — is also “a control that enable[s] users to reveal or hide their associated sections of content.” A heading for page structure and a button for interaction, at once — exactly what a plain <div> fails to express.

Why a div-based accordion looks fine and works for no one

1. No exposed name, role, or state (fails SC 4.1.2 Name, Role, Value)

WCAG SC 4.1.2 Name, Role, Value (Level A) exists so that “Assistive Technologies (AT) can gather appropriate information about, activate (or set) and keep up to date on the status of user interface controls in the content.” The Understanding doc is direct about custom controls: “If custom controls are created… or interface elements are programmed… to have a different role and/or function than usual, then additional measures need to be taken to ensure that the controls provide important and appropriate information to assistive technologies.”

A <div class="accordion-header">Shipping &amp; Returns</div> with a click handler has none of that. A screen reader announces it as plain text — no indication it can be activated, and no way to tell whether the panel below it is currently open or closed. A sighted user sees a chevron icon rotate; a screen reader user gets nothing.

2. Click-only headers with no keyboard equivalent (fails SC 2.1.1 Keyboard)

WCAG SC 2.1.1 Keyboard (Level A) requires that “wherever possible, content can be operated through a keyboard or keyboard interface.” The named failure for exactly this case is Failure Technique F54 — “Failure of Success Criterion 2.1.1 due to using only pointing-device-specific event handlers (including gesture) for a function.” An onclick-only handler on a non-interactive element is the textbook case: a <div> isn’t in the page’s Tab order unless tabindex was added by hand, and even then, an onclick handler alone still doesn’t fire on a keypress.

WCAG SC 1.3.1 Info and Relationships (Level A) requires that “information and relationships that are implied by visual or auditory formatting are preserved when the presentation format changes” — such as when a page is read by a screen reader instead of looked at. A sighted user infers “Shipping & Returns” controls the panel below it from proximity and an indent alone. Nothing in a bare <div> states that relationship programmatically, and nothing marks the header as a heading either — so a screen reader user skimming by heading, a common navigation strategy, won’t find it at all.

The fix: the APG Accordion pattern

Per the APG’s Roles, States, and Properties section, the pattern is:

  • “The title of each accordion header is contained in an element with role button.”
  • “Each accordion header button is wrapped in an element with role heading that has a value set for aria-level that is appropriate for the information architecture of the page.”
  • aria-expanded — "true" when the panel is visible, "false" when it isn’t.
  • aria-controls — “set to the ID of the element containing the accordion panel content.”
  • Optionally, role="region" on the panel container with aria-labelledby pointing back at its button — though the APG cautions against this “in circumstances that create landmark region proliferation, e.g., in an accordion that contains more than approximately 6 panels that can be expanded at the same time.”
<h3>
  <button type="button" aria-expanded="false" aria-controls="panel-shipping" id="header-shipping">
    Shipping &amp; Returns
  </button>
</h3>
<div id="panel-shipping" role="region" aria-labelledby="header-shipping" hidden>
  <!-- shipping and returns content -->
</div>

Using a native <button> inside a native <h3> (rather than role="button"/role="heading" on generic elements) gets keyboard operability for free — Enter and Space both trigger a <button> by default, no keyboard event handler needed. The hidden attribute (or a CSS display toggle paired with aria-expanded) controls visibility; the JavaScript only needs to flip aria-expanded and toggle hidden together, in the same step, so the two never drift out of sync.

Keyboard behavior: simpler than tabs, on purpose

The APG’s keyboard interaction for accordions is short: “Enter or Space: When focus is on the accordion header for a collapsed panel, expands the associated panel… When focus is on the accordion header for an expanded panel, collapses the panel if the implementation supports collapsing.” Tab and Shift+Tab move to the next or previous focusable element, with “all focusable elements in the accordion… included in the page Tab sequence.” Notably, the pattern documents no arrow-key requirement between headers — unlike the Tabs pattern this site has covered separately, an accordion needs no custom arrow-key code. A native <button> in the normal Tab order already satisfies the entire keyboard-interaction section.

One panel open, or several? And when collapsing isn’t allowed

Some accordions allow only one panel open at a time, auto-collapsing the previous one; others let every panel stay open independently. Both are valid — the APG notes that “some implementations require one panel to be expanded at all times and allow only one panel to be expanded; so, they do not support a collapse function.” For a “read more” section that, once expanded, can’t be re-collapsed, the pattern has a specific state: “If the accordion panel… is visible, and if the accordion does not permit the panel to be collapsed, the header button element has aria-disabled set to true.” That’s more accurate than leaving a control that silently does nothing on a second click.

What a scan catches, and what a person has to check

An automated scanner reliably flags a bare <div> header with no role at all. What it generally can’t verify: whether aria-expanded actually flips when the panel opens (a common bug — set once on load, never updated), whether aria-controls points at the correct panel after a copy-paste across FAQ items, and whether expanded content is genuinely reachable and readable with a screen reader, not just present in the DOM with hidden removed. Confirming those needs a keyboard and a screen reader, not just a check that an ARIA attribute exists in the markup.

Accordion vs. Tabs — a different fix for a different shape

The two patterns get mixed up regularly: an accordion shows and hides independent regions that can, depending on the implementation, be open at once, while tabs switch between mutually exclusive panels where exactly one is ever visible. “Description / Specs / Shipping” content that replaces itself in one panel is Tabs; FAQ questions that each expand independently is Accordion. Using the wrong pattern’s ARIA on the right layout is itself a common source of the aria-expanded-never-updates bug above.

FAQ

Does every accordion panel need role="region"? No — it’s explicitly optional in the pattern, and the APG recommends against it once an accordion has more than roughly six simultaneously-expandable panels, since each region becomes a landmark a screen reader user has to navigate past.

Can I use <details>/<summary> instead of building this by hand? Native <details>/<summary> handle the button role, expanded state, and keyboard support automatically in modern browsers, and work fine for simple single-panel disclosures. For a multi-panel accordion with shared open/close behavior, most teams still build the explicit pattern above for more control over styling and animation — either approach can meet the same criteria if implemented correctly.

Get your components checked by a person, not just a parser

Quietramp’s $890 audits include a manual pass through interactive components like accordions and expandable sections — checking role, state, and keyboard behavior with an actual screen reader, not just confirming that an ARIA attribute exists somewhere in the DOM. See a real sample report or check pricing.


This is educational content about technical accessibility practice, not legal advice. Meeting WCAG 2.1 AA does not constitute a legal compliance determination under the ADA or any other standard — consult qualified counsel for legal risk questions.

This article was drafted with AI assistance and reviewed by a person for accuracy before publication.

← Back to Notes