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 & 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.
3. No structural link between the header and its panel (fails SC 1.3.1 Info and Relationships)
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
headingthat has a value set foraria-levelthat 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 witharia-labelledbypointing 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 & 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.