Topic: Web platform
<details> Is Disclosure, Not an Accordion System
Native details already handles toggling and keyboard interaction. name can create exclusive panels, but only when that matches content semantics and safe fallback behavior.
Animated meme (expand/collapse)
A FAQ or a short explanation often starts with one isOpen. Then it grows a click handler, keyboard logic, aria-expanded, animation state, and a test matrix. None of that is wrong. The problem is rebuilding browser behavior before checking whether the product needs anything more.
<details> is valuable not because it means “never use JavaScript,” but because it provides a small, explicit contract: a person activates a summary to reveal additional information. The browser handles toggling, keyboard interaction, and basic semantics; the author explains what will be revealed.
My rule is:
When supplementary content can open independently, start with
<details>. Build a custom accordion only when the currently open item is product state, or the interaction is no longer a disclosure.
Treat it as a disclosure before treating it as a component
The smallest version already has an activatable control. Do not wrap summary in another button, and do not manually maintain aria-expanded:
<section aria-labelledby="shipping-title">
<h2 id="shipping-title">Shipping</h2>
<details>
<summary>When will I receive a tracking number?</summary>
<p>We send an email after the carrier receives the package.</p>
</details>
</section>
This structure fits FAQs, technical notes, optional advanced settings, error details, and side paths inside long-form content. It does not pretend the information disappeared: the person can still see a meaningful question and decide whether to reveal the answer.
A few small rules matter more than a polished animation:
summarymust be the first child, and its text should say what opening it reveals. “More” is rarely enough.- Put the real section heading outside
summary. MDN notes that heading roles insidesummarycan be inconsistent across browser and assistive-technology combinations. - Do not put persistent menus, destructive actions, or other interactive controls inside
summary. A trigger containing another trigger is usually evidence that this is no longer a simple disclosure.
Native HTML is not a free accessibility pass. It simply handles the common, easy-to-miss pieces before you add anything else.
name can make an accordion. Ask whether one-open-only is actually needed.
Give related <details> elements the same name, and the browser closes one when another opens:
<section aria-labelledby="plan-title">
<h2 id="plan-title">Plan details</h2>
<details name="plan">
<summary>Individual plan</summary>
<p>For one person and a small project.</p>
</details>
<details name="plan">
<summary>Team plan</summary>
<p>Shared permissions and central administration.</p>
</details>
</section>
That is a useful little feature, but it should not become the default for every FAQ. When people compare two answers, return to a previous step, or troubleshoot several things at once, forcing one panel closed is friction.
Before choosing name, answer this in plain language: does opening the second item make the first item going away better? If the answer is not clearly yes, let each item stay independent.
The basic <details> toggle is widely supported; check the product browser matrix for name grouping. This is a good progressive enhancement boundary: an older browser that does not enforce exclusivity still reveals and reads every panel. If one-open-only is a non-negotiable workflow rule, do not stake it on a convenient no-JavaScript attribute.
Animated meme (expand/collapse)
When a custom accordion is the more honest choice
The HTML Standard makes the same distinction: <details> represents a disclosure, not a universal stand-in for tabs or menus. When these requirements appear, a custom implementation is usually clearer:
| Requirement | Why it is not plain <details> |
Starting point |
|---|---|---|
| Switching between pages of content | The person selects a view instead of revealing supporting information | Tabs pattern |
| Persistent actions next to the header | One summary should not contain several interactive controls | Heading + button + separate action |
| Open state belongs in the URL, survives navigation, or synchronizes with others | open has become application state |
Explicit state model |
One-open-only must hold where name is unsupported |
Multiple panels break the task | Custom accordion plus tests |
| Specific arrow-key behavior, focus management, or complex animation | The interaction contract exceeds native disclosure | Implement the WAI-ARIA pattern |
This is not native HTML being weak. It is avoiding the mistake of spending the saved state management on imitating another widget. When the requirement is truly an accordion, WAI-ARIA Authoring Practices defines the heading, button, aria-expanded, aria-controls, and keyboard contract. A testable component is cheaper than piling exceptions on <details>.
Save JavaScript for synchronization, not the toggle
When opening a panel must load expensive data, record use, or synchronize another non-critical UI, the toggle event is a clean extension point:
const details = document.querySelector("#release-notes");
details.addEventListener("toggle", () => {
if (!details.open) return;
loadReleaseNotesOnce();
});
The code does not take over toggling. It adds work only after the browser has changed state. Tests can therefore establish readable content and keyboard toggling first, then check that loading happens only when necessary.
That placement is usually easier to maintain than copying open into framework state immediately. The exception is real: if framework state truly must be the source of truth, make it so deliberately. Do not store it in two places.
Five code-review questions
- Is this supplementary information, or another selectable view?
- Will people need multiple items open to compare or act?
- Does the summary say what opening it reveals?
- Can unsupported
namegrouping fall back to multiple open panels without blocking the task? - If a custom widget is needed, has its actual accessibility contract been tested?
<details> is strongest when it makes “readable content first, interaction second” the default. Use it for disclosure and you remove a layer of state and listeners. When requirements outgrow it, choose the correct control. Both choices are more honest than forcing one universal accordion.