Accordion
A native accordion built with <details> and <summary>. The shared name attribute makes the group exclusive, so opening one panel closes the others.
0 KB JavaScript
~30 CSS lines
details exclusive group
keyboard native access
Preview
What is this project?
A demonstration of everything modern CSS and HTML can do without a single line of JavaScript. Carousel, modals, tabs, tooltips, validated forms… all of it.
How does this accordion work?
The <details> element has been native for a long time, but the name="..." attribute shared across multiple <details> is recent. It creates an accordion where only one entry can be open at a time.
Which browsers support this?
All Chromium-based browsers, recent Firefox, and recent Safari support these features. For older browsers, the markup remains usable, just without the exclusive behaviour.
Why no JavaScript?
Because the web platform has become powerful enough. Less JS = fewer bugs, better performance, more free accessibility, and a site that works even when JS fails to load.
The code
The markup
<div class="accordion">
<details name="accordion-1" open>
<summary>
<!-- Section 1 -->
<span class="chev" aria-hidden="true">+</span>
</summary>
<!-- Content 1 -->
</details>
<details name="accordion-1">
<summary>
<!-- Section 2 -->
<span class="chev" aria-hidden="true">+</span>
</summary>
<!-- Content 2 -->
</details>
...
</div>CSS mechanisms
/* ----------------------------------------------------------------------------
* Details
* ---------------------------------------------------------------------------- */
/**
* If you need to support browsers without `::details-content`
* (pre-late 2024), add `overflow: hidden` to `.accordion > details`
* to prevent visual overflow during CSS transitions.
* On current browsers, clipping is handled by `overflow: clip`
* directly on `::details-content`.
*/
.accordion > details {
/* add your styles here */
}
/**
* styles of the open `<details>`
*/
.accordion > details[open] {
/* add your styles here */
}
/* ----------------------------------------------------------------------------
* Summary
* ---------------------------------------------------------------------------- */
/**
* Summary row: removes default disclosure marker, lays out title
* and chevron with space-between.
*/
.accordion > details > summary {
list-style: none;
cursor: pointer;
/* add your styles here */
}
/**
* Hover state on summary row.
*/
.accordion > details > summary:hover {
/* add your styles here */
}
/**
* Hide the legacy WebKit disclosure triangle.
*/
.accordion > details > summary::-webkit-details-marker {
display: none;
}
/* ----------------------------------------------------------------------------
* Chevron
* ---------------------------------------------------------------------------- */
/**
* Chevron / "+" indicator that rotates 45° when open (turning
* "+" into "×").
*/
.accordion > details > summary > .chev {
transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
/* add your styles here */
}
/**
* Open state: rotate chevron to form an "×".
*/
.accordion > details[open] > summary > .chev {
transform: rotate(45deg);
/* add your styles here */
}
/* ----------------------------------------------------------------------------
* Open/Close Animation
* ---------------------------------------------------------------------------- */
/**
* Animated open/close using the new `::details-content` pseudo
* and `interpolate-size: allow-keywords` which unlocks transitions
* between `0` and `auto` block sizes.
*
* `content-visibility` is transitioned discretely so the browser
* keeps the collapsed/rendered state in sync with the open/close
* transition.
*/
@supports selector(details::details-content) and (interpolate-size: allow-keywords) {
/**
* Enables transitions to and from `auto` sizes globally.
* Scoped inside `@supports` so it only applies in browsers
* that understand `interpolate-size`.
*/
.accordion {
interpolate-size: allow-keywords;
}
.accordion > details::details-content {
block-size: 0;
overflow: clip;
transition: block-size 320ms cubic-bezier(0.22, 1, 0.36, 1), content-visibility 320ms allow-discrete;
}
.accordion > details[open]::details-content {
block-size: auto;
}
}Benefits
-
01.
Semantic markup
Clear structure:
<details>contains one<summary>followed by flow content. Readable without CSS. -
02.
Native behavior
The browser handles toggling, keyboard activation, focus, and open/closed state without manual ARIA.
-
03.
Low runtime cost
No JavaScript state, no hydration, no re-rendering. The disclosure behavior is handled by the platform.
-
04.
DOM content
Panel content remains in the document, available to search, indexing, copy/paste, and find-in-page.
Current limitations
-
01.
Animation support
Smooth height animation depends on
::details-contentandinterpolate-size. Without both, the accordion still works but falls back to an instant toggle. -
02.
Marker styling
The native marker can be customized with
::marker, but cross-browser cleanup still often requires::-webkit-details-marker. -
03.
Assistive tech differences
Screen readers may announce summaries and state changes differently. Keep
<summary>simple and test real combinations before adding ARIA.