Animate <details> with ::details-content and interpolate-size

Animating details open and close without measuring height

<details> has always snapped open and closed. Animating it meant reading scrollHeight in JS, setting a max-height, transitioning, and unsetting on close. ::details-content plus interpolate-size animates the real height natively.

Old way JS height measurement
const details = document.querySelectorAll('details');details.forEach((d) => {  const content = d.querySelector('.content');  d.addEventListener('toggle', () => {    if (d.open) {      const h = content.scrollHeight;      content.style.height = '0px';      requestAnimationFrame(() => { content.style.height = h + 'px'; });    } else {      content.style.height = content.scrollHeight + 'px';      requestAnimationFrame(() => { content.style.height = '0px'; });    }  });});
two CSS rules
:root {  interpolate-size: allow-keywords;}details::details-content   {  height: 0;  overflow: clip;  transition: height .3s ease, content-visibility .3s ease allow-discrete;}details[open]::details-content   {  height: auto;}/* native details, animated open and close */
Newly available Since 2025 88% global usage

Since 2025 this feature works across the latest devices and browser versions. This feature might not work in older devices or browsers.

Works in all modern browsers. May need a fallback for older browsers.

131+
143+
18.4+
131+
click the toggle. The content slides open and closed, no JS.
What does ::details-content target?
Everything inside the <details> except the <summary>. The browser wraps the rest in an anonymous box you can finally style and animate.
Why does this need interpolate-size?
Without interpolate-size: allow-keywords, height: auto still snaps. The keyword tells the browser it is allowed to interpolate between a fixed value and an intrinsic one.
native details, transitioned by ::details-content

Real height, animated

interpolate-size: allow-keywords turns height: auto into something the browser can interpolate. No more fake max-height ladders or hidden measuring divs.

Native semantics intact

<details> still toggles, screen readers still announce, keyboard still works. The animation is layered on top, not replacing native behavior.

Targets the content directly

::details-content wraps everything inside <details> except the summary. Style it like any element: padding, transitions, transforms.

Key Change
Animate auto
interpolate-size unlocks it
Old Approach
JS height measurement
Read scrollHeight, transition, unset
Modern Approach
::details-content + interpolate-size
CSS only

How it works

Two pieces have to line up. First, interpolate-size: allow-keywords on the root tells the browser to interpolate between fixed values and intrinsic keywords like auto. Without it, height: auto is still a hard switch.

Second, ::details-content selects everything inside <details> except the <summary>. Set height: 0 in the closed state and height: auto in the open state, then transition between them. The allow-discrete keyword on the content-visibility transition keeps the content visible during the closing animation.

The same trick works for any container that needs to grow or shrink to fit its contents. Wrap the toggle in <details> and you get native keyboard handling, ARIA roles, and animated open and close, all from the platform.

ESC