Accordion
A native <details> styled via a single class. Animated open and close via ::details-content + the CSS Grid 0fr → 1fr trick + transition-behavior: allow-discrete. Single-open grouping via the native name attribute. Two variants, three sizes, color theming. No JavaScript.
Install
import "elements-kit/ui/styles.css";
// pick a neutral palette for --neutral-* (must match data-neutral on root):import "elements-kit/ui/styles/palette/gray.css";import "elements-kit/ui/styles/neutral/gray.css";// and the accordion itself:import "elements-kit/ui/accordion/accordion.css";API
<!-- Single-open group: same `name` on all items --><details class="x-accordion" name="faq" data-size="2"> <summary>How does it work?</summary> <p>Any HTML inside.</p></details><details class="x-accordion" name="faq"> <summary>Is it accessible?</summary> <p>Native disclosure semantics.</p></details>
<!-- Multi-open: omit `name` --><details class="x-accordion"> <summary>Standalone</summary> <p>Content.</p></details>| Attribute | Values |
|---|---|
name (native HTML) | any string — items sharing a name form an exclusive group |
data-variant | surface (default — bordered card), soft (tinted block, no border), borderless (no chrome — color-only hover) |
data-size | 1, 2 (default), 3 |
data-accent (on parent or self) | any imported color scale |
aria-disabled="true" | greys out and stops clicks; screen readers announce as disabled |
open (native HTML) | initial open state |
Sizing
data-size | summary padding | font-size |
|---|---|---|
1 | --space-2 / --space-3 | --font-size-2 |
2 (default) | --space-3 / --space-4 | --font-size-3 |
3 | --space-4 / --space-5 | --font-size-4 |
Variants
surface(default) —--color-materialbackground with a soft--neutral-a5border and--radius-4corners. In light mode the body sits at--neutral-a2; in dark mode that tint moves to the summary. Stacked siblings collapse their adjoining borders into one card.soft— surface minus the border. Shares the panel bg, per-mode alpha flip on summary/body, and hover behavior; just no1px solidring around it. Mid-emphasis betweensurfaceandborderless.borderless— no border, no background, no horizontal padding. Hover affordance is a text-color shift (a12 → 12) — no bg wash. The trigger color also stays at full strength while[open]. For FAQs inside body copy.
The primitive ships no chevron — keep it quiet by default. If you want one, place an SVG (or anything else) inside <summary> and rotate it with your own [open] > summary svg { transform: rotate(180deg); } rule. See the playground for an example.
Single-open grouping
Browser-enforced via the native name attribute. Opening one item closes its siblings.
<details class="x-accordion" name="faq"><summary>One</summary>…</details><details class="x-accordion" name="faq"><summary>Two</summary>…</details><details class="x-accordion" name="faq"><summary>Three</summary>…</details>Omit name and each item opens independently.
Animation
Three pieces cooperate to animate both open and close, cross-browser:
::details-content— addresses the content wrapper as a pseudo-element so we can style it without an extra DOM node.display: grid; grid-template-rows: 0fr → 1fr— the classic grid trick. The implicit row track holds the rendered children of<details>(minus<summary>). Transitioning the track between0frand1fris universal CSS — nointerpolate-sizeneeded, so it works in every current browser.padding-block-endis animated alongside so the bottom padding grows in lockstep.transition-behavior: allow-discreteoncontent-visibility— defers the browser’s instant content-hide until the row collapse finishes, so the close animation runs to completion.
Without allow-discrete, the browser would yank the content the moment [open] flipped off and the close would be invisible.
Respects prefers-reduced-motion: reduce — transitions are removed.
States
[open]— content expanded.:focus-visibleonsummary— 2px--focus-8outline at-2pxinset offset.aria-disabled="true"—0.6opacity,pointer-events: noneon the summary.<details>has no nativedisabledand isn’t a form control, so:disableddoesn’t apply;aria-disabledis the spec-correct hook and is announced by screen readers.
Nested accordions
Nest a <details class="x-accordion"> inside another’s content. Cascade Just Works — sizing tokens scope per item, single-open grouping scopes per name.
<details class="x-accordion" name="outer"> <summary>Parent</summary> <details class="x-accordion" name="inner"> <summary>Child</summary> <p>Deep content.</p> </details></details>Theming
Set data-accent="<color>" on the accordion or any ancestor to theme the accent. Light/dark flips automatically via the .dark class on a parent — see Light & dark.
Accessibility
<details> / <summary> is a native disclosure widget. Keyboard support (Enter, Space to toggle) and screen-reader announcement (disclosure triangle, collapsed/expanded) come from the browser.
- Put the trigger label in
<summary>directly — wrap with<h3>inside the summary only if document outline matters. Don’t wrap<details>in a heading. - For long content panels, the disclosure region is implicitly labeled by the summary. No
aria-controlsneeded.
Browser support
| Feature | Chrome | Safari | Firefox |
|---|---|---|---|
<details name> grouping | 120+ | 17.4+ | 129+ |
::details-content | 131+ | 18.2+ | 137+ |
transition-behavior: allow-discrete | 117+ | 17.4+ | 129+ |
grid-template-rows transition | universal | universal | universal |
All current browsers support the full set. Older browsers degrade to instant open and close — disclosure still works; grouping falls back to multi-open. No JavaScript fallback is shipped.