A collapsible list of items — click a header, reveal (or hide) its content. Structure first, then three versions: the native, zero-JS one, a native one-at-a-time variant, and a custom JS version that can actually animate.
Pseudocode
Structure
Each item is a header (the always-visible, clickable part) plus a body (the part that collapses).
A list of these items, stacked vertically, forms the accordion.
The <details>/<summary> pair is a native HTML version of exactly this — the browser handles the open/closed state, no JS or class="active" needed for the basics.
Style
The header gets the cursor/hover styling and a marker (the default disclosure triangle, or a custom +/−) showing whether it’s open.
Height changes are either instant (the native default) or animated — animating means abandoning <details> for a custom button + div pair, since transitioning a native details element’s open/close height isn’t reliably supported yet.
Features
Multiple open at once is the default <details> behavior — nothing to build.
Exactly one open at a time can also be done natively: give every <details> in the group the same name attribute and the browser closes the others for you.
Animating the open/close height needs JS: measure the panel’s content height and transition between 0 and that value.
Basic static accordions
The <details>/<summary> pair is a native, built-in accordion item — click the summary, the browser handles opening and closing. No JS, no transition, and every item is independent: any number can be open at once.
<div class="accordion"> <details> <summary>What's your return policy?</summary> <p>Unworn items can be returned within 30 days for a full refund.</p> </details> <details> <summary>Do you ship internationally?</summary> <p>Yes — shipping costs are calculated at checkout based on destination.</p> </details> <details> <summary>How do I track my order?</summary> <p>You'll get a tracking link by email as soon as your order ships.</p> </details></div>
Give every <details> the same name attribute and the browser makes them mutually exclusive — opening one closes the others, still with zero JS. (Supported in current Chrome, Edge, Firefox and Safari; older browsers just fall back to every item being independent, like the section above.)
<div class="accordion"> <details name="faq"> <summary>What's your return policy?</summary> <p>Unworn items can be returned within 30 days for a full refund.</p> </details> <details name="faq"> <summary>Do you ship internationally?</summary> <p>Yes — shipping costs are calculated at checkout based on destination.</p> </details> <details name="faq"> <summary>How do I track my order?</summary> <p>You'll get a tracking link by email as soon as your order ships.</p> </details></div>
<details> can’t be reliably height-animated yet, so this version swaps it for a plain <button> + <div class="accordion-panel"> pair, with JS doing what the browser did for free above: tracking open/closed state (via aria-expanded, which does double duty for accessibility) and closing every other item when one opens.
The animation is the same trick as the tabs note: height: 0 by default, and opening sets height to the panel’s measured scrollHeight in pixels. scrollHeight reports the full content height even while the panel is visually collapsed and clipped by overflow: hidden, so there’s no flash of the wrong size — closing just sets it back to 0px.
Same caveat as before: the pinned height doesn’t know about content that reflows later (a window resize, a font loading in) — a production version would re-measure rather than trust a value read once at open time.
<div class="accordion"> <div class="accordion-item"> <button class="accordion-trigger" aria-expanded="false">What's your return policy?</button> <div class="accordion-panel"> <p>Unworn items can be returned within 30 days for a full refund.</p> </div> </div> <div class="accordion-item"> <button class="accordion-trigger" aria-expanded="false">Do you ship internationally?</button> <div class="accordion-panel"> <p>Yes — shipping costs are calculated at checkout based on destination.</p> </div> </div> <div class="accordion-item"> <button class="accordion-trigger" aria-expanded="false">How do I track my order?</button> <div class="accordion-panel"> <p>You'll get a tracking link by email as soon as your order ships.</p> </div> </div></div>