Accordion
Collapsible sections built on native details and summary elements. No JavaScript.
The accordion is a .accordion wrapping native <details> and <summary> elements.
Open/close state, keyboard support, and the height transition (via ::details-content) are
handled by the browser.
Default
<div class="accordion"> <details class="border-b py-sm"> <summary class="font-strong hover:text-muted-foreground"> What is an accordion? <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"> <rect width="256" height="256" fill="none" /> <polyline points="208 96 128 176 48 96" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" /> </svg> </summary> <div class="pb-sm"> <p> A disclosure widget built from the native <code><details></code> and <code><summary></code> elements. The open/close animation uses the <code>::details-content</code> pseudo-element — no JavaScript. </p> </div> </details> <details class="border-b py-sm"> <summary class="font-strong hover:text-muted-foreground"> How does it work? <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"> <rect width="256" height="256" fill="none" /> <polyline points="208 96 128 176 48 96" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" /> </svg> </summary> <div class="pb-sm"> <p> Each item is a separate <code><details></code>, so several can stay open at once. Click a summary or focus it and press Enter to toggle. </p> </div> </details> <details class="border-b py-sm"> <summary class="font-strong hover:text-muted-foreground"> Why use it? <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"> <rect width="256" height="256" fill="none" /> <polyline points="208 96 128 176 48 96" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" /> </svg> </summary> <div class="pb-sm"> <p> Keyboard support, state, and accessibility come from the platform — Zazz only styles the chrome through theme tokens. </p> </div> </details></div>/** * _accordion.css — Accordion (.accordion on <details>/<summary>) * * @layer variables, reset, components * @requires layers.css, _variables.css * @uses ::details-content — native open/close block-size transition (reset layer) * @uses interpolate-size: allow-keywords — from _variables.css; required for * block-size: 0 → auto transitions * @tokens --accordion-* (@layer variables) */@layer variables { :root { --accordion-icon-size: var(--step-4); --accordion-icon-transform-open: rotate(-180deg); }}@layer reset { /* =========================================================================== DETAILS & SUMMARY — native open/close transition + summary marker reset - In the reset layer so the .accordion component styling below wins. =========================================================================== */ :where(details)::details-content { --details-content-transition: opacity var(--default-transition-duration) var(--default-transition-timing-function), transform var(--default-transition-duration) var(--default-transition-timing-function), block-size var(--default-transition-duration) var(--default-transition-timing-function), content-visibility var(--default-transition-duration) var(--default-transition-timing-function) allow-discrete; block-size: 0; overflow: hidden; transition: var(--details-content-transition); will-change: opacity, transform, block-size, content-visibility; } :where(details[open])::details-content { block-size: auto; } :where(summary) { display: flex; text-decoration: none; user-select: none; list-style: none; }}@layer zazz.components { .accordion { display: grid; inline-size: 100%; flex: 1; } .accordion > details { display: grid; flex: 1 0 auto; } .accordion > details > summary { display: flex; align-items: center; justify-content: space-between; /* ring renders as box-shadow; the ring widths animate from 0 so the ring grows in smoothly on focus */ --_ring-offset-width: 0px; --_ring-width: 0px; box-shadow: 0 0 0 var(--_ring-offset-width) var(--ring-offset-color), 0 0 0 calc(var(--_ring-offset-width) + var(--_ring-width)) var(--ring); /* transparent twin of the ring for forced-colors / high-contrast modes */ outline: var(--outline-width) var(--outline-style) transparent; outline-offset: var(--outline-offset); transition: var(--default-transition); } .accordion > details > summary:focus-visible { --_ring-offset-width: var(--ring-offset-width); --_ring-width: var(--ring-width); outline-color: transparent; } .accordion summary > svg { display: flex; align-items: center; justify-content: center; inline-size: var(--accordion-icon-size); block-size: var(--accordion-icon-size); pointer-events: none; transition: transform var(--default-transition-duration) var(--default-transition-timing-function); } .accordion > details[open] > summary > svg { transform: var(--accordion-icon-transform-open); }}There are no data-* attributes to configure. Author each panel as a <details class="border-b">
with a <summary class="font-strong hover:text-muted-foreground">, an inline chevron <svg>
that rotates on open, and a <div class="pb-sm"> body. Multiple panels can be open at once by
default; give a group of <details> the same name to make them mutually exclusive.
Theming runs through tokens: --accordion-summary-padding-block, --accordion-icon-size, and
--accordion-icon-transform-open (the open-state chevron rotation).