When to use
Use an accordion when a set of related sections each have content the user might want to read but probably doesn’t. FAQs, settings groups, “what’s included” tables on pricing pages.
Don’t use an accordion when:
- the user almost certainly wants the content (it should just be visible),
- there’s only one section (just show it),
- the content is very short (a paragraph each — list it instead),
- the sections aren’t peers (use a real heading hierarchy instead).
The pattern
When is the next issue?
Sundays at 7am UTC. We have never missed one. (Lena once nearly did, in week 14, snowed in.)
Can I gift a subscription?
Yes. Email us with the recipient and we set it up — no awkward gift card, no email to them until they want it.
Do you have an RSS feed?
Members get a private RSS feed with the full text of every issue. Public preview feed is on the way.
In 2026, the answer is <details> and <summary>. The browser:
- makes the
<summary>focusable, - toggles the
[open]attribute when activated, - announces “expanded” / “collapsed” on state change,
- handles Enter and Space,
- closes on Escape when used inside
<dialog>.
<details>
<summary>When is the next issue?</summary>
<p>Sundays at 7am UTC. We have never missed one.</p>
</details> That’s a complete, accessible accordion item. Stack several of them and you have an accordion group.
Letting only one open at a time
If you need exclusive accordion behaviour (open one, close the others), give every <details> element the same name attribute. It’s an HTML feature, no JS:
<details name="faq"><summary>One</summary>…</details>
<details name="faq"><summary>Two</summary>…</details>
<details name="faq"><summary>Three</summary>…</details> Activating one closes the others — like a radio group, but for disclosures.
Styling the marker
Browsers render a default disclosure triangle inside <summary>. Replace it with whatever you like:
summary {
list-style: none; /* Firefox + Chromium */
}
summary::-webkit-details-marker {
display: none; /* old Safari */
}
/* now add your own indicator */
summary::after {
content: '+';
margin-inline-start: auto;
transition: transform 150ms;
}
details[open] summary::after {
transform: rotate(45deg); /* + becomes × */
} Don’t remove the indicator entirely — sighted users need a visual cue that the summary is interactive.
Anti-pattern: rolling your own with divs
The classic version, found in countless component libraries:
<div class="accordion">
<div class="accordion__header" onclick="toggle(this)">
When is the next issue?
</div>
<div class="accordion__panel hidden">
Sundays at 7am UTC.
</div>
</div> The complete list of failures:
- Header isn’t focusable (no
tabindex) — keyboard users can’t reach it. - No keyboard handler — even with
tabindex, Enter/Space don’t toggle (needkeydownhandler). - No role announced — screen readers say “When is the next issue” with no indication that it’s interactive.
- No state announced — opening it changes nothing audible. The user can’t tell if their action did anything.
- No relationship between header and panel —
aria-controlswould name the panel;aria-expandedwould announce the state. Neither is here.
To make it actually work you’d need: tabindex="0", role="button", aria-expanded="true|false", aria-controls="panel-id", a keydown handler for Enter/Space, plus the click handler. That’s a lot of code to reinvent <details>.
If you absolutely cannot use <details> (e.g. you need the panel to render outside the summary’s DOM), use <button> for the header — at least you’ll get role and keyboard for free:
<h3>
<button type="button" aria-expanded="false" aria-controls="faq-1">
When is the next issue?
</button>
</h3>
<div id="faq-1" hidden>
Sundays at 7am UTC.
</div> Common variations
- Inside a heading — wrap each
<summary>in the appropriate heading level (<h3><summary>…</summary></h3>won’t work —<summary>must be the first child of<details>. Instead, put the heading inside the summary:<summary><h3>…</h3></summary>). - All-open by default — add
opento the<details>you want expanded on first paint. Useful for the “active” item in a settings page. - Animated open/close — Chromium and Safari now support
interpolate-size: allow-keywordsandtransition: content-visibilityto animate to/fromauto. Firefox is catching up. Until cross-browser, an instant snap is honest about the state. - Anchored — give each
<details>anidand link to it from elsewhere; activating the link expands the panel and scrolls into view (browsers handle this automatically).
Checklist
-
<details>and<summary>(or<button aria-expanded>if you must roll your own) - Visible focus indicator on
<summary> - Visual indicator (chevron, +/−) for the open state
- If exclusive, use the
nameattribute — not custom JS - Hit area of
<summary>at least 44×44 CSS px - Don’t trap focus inside the panel — Tab should leave it normally