Animate accordion height in CSS: from grid rows to interpolate-size

Smooth opening and closing without guessing a max-height: a grid technique that runs in every current browser, the newer interpolate-size and ::details-content features layered over native details, and the support facts you need to choose.

CSS recipe · By AurumCodeWorks · 7 min read · Updated 12 October 2026

Standard delivery takes three to five working days. Order before 2pm for next-day delivery, or collect from any of our shops at no charge.
Grid rows — grid-template-rows from 0fr to 1fr on a button-driven panel; animates in every current browser.
Returns policy
Send back any unworn item within 30 days for a full refund. Print the prepaid label from your order confirmation and drop the parcel at a post office.
Native details — ::details-content plus interpolate-size: slides in Chrome and Edge, fades in Safari and Firefox, and simply toggles anywhere older.
Starter
One site, shared hosting and email support during office hours.
Team
Five sites, staging environments and a named contact.
Agency
Unlimited sites, client billing and a phone line that answers at weekends.
Exclusive group — three <details name="ac-plan"> elements: opening one closes the others, with no script.

Why height: auto would not transition

A transition needs two values the browser can calculate between. 0 and 240px are both lengths, so every frame in between is simple arithmetic. auto is not a length but an instruction to work the height out from the content, and for most of CSS’s history intrinsic keywords like it could not be interpolated. Change height from 0 to auto and the panel jumps to full size, whatever transition says.

Two workarounds became habits. The first transitions max-height to a number you hope exceeds the content, say 1000px. The duration then covers all 1,000 pixels: a 200px panel finishes opening in the first fifth of the animation, and on closing nothing visibly moves until max-height drops below the content, which feels like lag. Guess too low and long content is cut off, perhaps only on a narrow phone.

The second uses JavaScript: read scrollHeight, set it as an explicit height, then restore auto on transitionend. Closing runs in reverse and needs a forced reflow between the two writes, or nothing animates. It works, but it is fragile, layout-thrashing code.

The grid-rows technique: works in every current browser

CSS grid offers a cleaner route. A row sized 1fr takes the full height of its content; a row sized 0fr takes none. Both are the same type of value, so grid-template-rows can transition between them, and because nothing is guessed the timing is right for any amount of content.

<div class="ac-grid">
  <button type="button" class="ac-grid-btn" aria-expanded="false" aria-controls="ac-grid-panel">Delivery options</button>
  <div class="ac-grid-panel" id="ac-grid-panel">
    <div class="ac-grid-inner">
      <div class="ac-text">Standard delivery takes three to five working days. …</div>
    </div>
  </div>
</div>
.ac-grid-panel {
  display: grid;
  grid-template-rows: 0fr;
  visibility: hidden;
  transition: grid-template-rows 0.35s ease, visibility 0.35s;
}
.ac-grid-btn[aria-expanded="true"] + .ac-grid-panel {
  grid-template-rows: 1fr;
  visibility: visible;
}
.ac-grid-inner {
  min-height: 0;
  overflow: hidden;
}
document.querySelectorAll('.ac-grid-btn').forEach((btn) => {
  btn.addEventListener('click', () => {
    const open = btn.getAttribute('aria-expanded') === 'true';
    btn.setAttribute('aria-expanded', String(!open));
  });
});

The inner wrapper is the part people forget. A grid item’s automatic minimum height is its content height, so without help the row refuses to shrink below the text. overflow: hidden resets that minimum to zero and clips the text; min-height: 0 states the intent explicitly. Put padding on an element inside the wrapper, never on the wrapper itself, or it stays visible when the row is 0fr.

The visibility transition is for accessibility. Clipped content is still in the page, so without it a keyboard user could tab into a link inside a closed panel. An element stays visible for the whole of a transition that has visible at either end, so the panel only hides once it has finished closing.

Firefox 66 was first to interpolate grid-template-rows (March 2019), then Safari 16 and Chrome 107 in autumn 2022. Every technique here animates layout, so the browser recalculates the page each frame; for a short panel that is cheap, and our guide to CSS animation performance explains when it matters.

Our accordion animations collection has a paste-ready version, the Smooth Accordion card, which drives the same grid from a checkbox and label without script. Its checkbox is hidden visually rather than with display: none, so it can still take keyboard focus, but screen readers announce it as a checkbox rather than an expandable section, so in production prefer a real button or native <details>.

interpolate-size: animating to auto directly

Chrome and Edge 129, released in September 2024, added a property that switches on what used to be impossible. interpolate-size: allow-keywords lets the browser animate between a length or percentage and an intrinsic keyword: auto, min-content, max-content, fit-content, and content for flex-basis. It is inherited, so the setup recommended in Chrome’s announcement, Animate to height: auto, is one declaration on the root element.

:root {
  interpolate-size: allow-keywords;
}

It is opt-in because, as MDN’s interpolate-size reference explains, many existing style sheets assume keyword sizes snap, and animating them by default would break those sites. One end of the transition must be a length or percentage, so auto to max-content still jumps. Browsers that do not know the property ignore it: in October 2026 that still means Firefox and Safari, where implementation bugs are open but nothing has shipped. The demos here scope it to .ac-details so no other transition on the site changes.

calc-size() is the per-value alternative. Its first argument is the keyword; in the second, the word size stands for the resolved value. Such a value animates even without interpolate-size, and allows arithmetic that calc() refuses, such as calc-size(auto, size + 2rem). Write a plain height: auto first as the fallback.

.panel {
  height: 0;
  overflow: clip;
  transition: height 0.35s ease;
}
.panel.is-open {
  height: auto;                  /* other browsers: no animation */
  height: calc-size(auto, size); /* Chrome and Edge 129+ */
}

A percentage inside the calculation resolves against the containing block, not size, so scale with a multiplier such as size * 1.5. Support matches interpolate-size exactly; reach for calc-size() only when you need the arithmetic.

Animating native details with ::details-content

A <details> element toggles instantly, and animating a wrapper div only half helps: on closing, the browser hides the content before any transition can play. The ::details-content pseudo-element selects the part that expands and collapses, which closes that gap. MDN’s ::details-content reference marks it Baseline 2025: Chrome and Edge 131 (November 2024), Safari 18.4 (March 2025) and Firefox 143 (September 2025).

<details class="ac-details">
  <summary class="ac-summary">Returns policy</summary>
  <div class="ac-text">Send back any unworn item within 30 days for a full refund. …</div>
</details>
.ac-details::details-content {
  opacity: 0;
  transition: opacity 0.35s ease, content-visibility 0.35s allow-discrete;
}
.ac-details[open]::details-content {
  opacity: 1;
}
@supports (interpolate-size: allow-keywords) {
  .ac-details {
    interpolate-size: allow-keywords;
  }
  .ac-details::details-content {
    height: 0;
    overflow: clip;
    transition: height 0.35s ease, opacity 0.35s ease, content-visibility 0.35s allow-discrete;
  }
  .ac-details[open]::details-content {
    height: auto;
  }
}

When a <details> closes, the browser’s own style sheet hides the content with content-visibility: hidden. That property is discrete, so it normally flips at once and cuts the closing animation short. The allow-discrete keyword, which sets transition-behavior, keeps the content rendered until the transition ends. Chrome’s post More options for styling details uses the same pairing.

This is progressive enhancement in three layers. Browsers without ::details-content drop the rules and show an ordinary <details>. Safari and Firefox, which have the pseudo-element but not interpolate-size, get the opacity fade. Chrome and Edge get the full slide. Keeping the height rules inside @supports means browsers that cannot animate them never apply the zero height at all.

Two gaps remain. Firefox does not yet transition content-visibility, so its fade plays only on opening. Safari cannot chain another pseudo-element after ::details-content, such as ::details-content::before.

One open at a time: the name attribute

Give several <details> elements the same name and the browser treats them as a group: opening one closes whichever was open. The third demo is only this markup with the classes above, and no script.

<div class="ac-group">
  <details class="ac-details" name="ac-plan" open>
    <summary class="ac-summary">Starter</summary>
    <div class="ac-text">One site, shared hosting and email support during office hours.</div>
  </details>
  <details class="ac-details" name="ac-plan">
    <summary class="ac-summary">Team</summary>
    <div class="ac-text">Five sites, staging environments and a named contact.</div>
  </details>
  <details class="ac-details" name="ac-plan">
    <summary class="ac-summary">Agency</summary>
    <div class="ac-text">Unlimited sites, client billing and a phone line that answers at weekends.</div>
  </details>
</div>

Chrome and Edge 120 and Safari 17.2 shipped this in December 2023, and Firefox 130 followed in September 2024. Older browsers ignore the attribute and let every item open independently, a perfectly usable fallback. Grouped elements need not be siblings, and if several carry open in the HTML, only the first in source order renders open.

Use it sparingly. An exclusive group suits short lists such as settings sections or the plans in the demo. In FAQs readers often want to compare two answers, which is why the FAQ on this page lets several stay open.

Why native details beats a div and a script

The grid demo needed a button, aria-expanded, aria-controls and a click handler, and still lacks things <details> provides free. A <summary> is focusable, toggles with Enter or Space, and is exposed to assistive technology with an expanded or collapsed state, so screen readers announce it without any ARIA. The usual accordion bugs, such as a stale aria-expanded or a focusable link inside a closed panel, come from reproducing that by hand. Our accessible website guide covers the wider checklist.

Find in page is the less obvious win. Chrome has opened a closed <details> when a find-in-page match is inside it since version 97, Safari since 26.2 (December 2025) and Firefox since 148 (February 2026), though Safari does not yet scroll the match into view correctly. Text in the grid demo’s closed panel cannot be found at all.

For a custom panel, hidden="until-found" restores searching: when a match is found the browser removes the attribute and fires a beforematch event, which your script should use to update aria-expanded. It works in Chrome and Edge 102, Firefox 148 and Safari 26.2; other browsers treat it as plain hidden.

Browser support in October 2026

Versions come from MDN’s browser compatibility data as published on 8 October 2026; Edge uses Chrome’s version numbers. Every fallback leaves the content readable.

TechniqueChrome/EdgeSafariFirefoxFallback behaviour
grid-template-rows 0fr → 1fr107 (Oct 2022)16 (Sep 2022)66 (Mar 2019)Panel opens and closes instantly
interpolate-size and calc-size()129 (Sep 2024)Not supportedNot supportedNo height animation; the final height applies at once
::details-content131 (Nov 2024)18.4 (Mar 2025)143 (Sep 2025)Rules ignored; a plain details that toggles instantly
content-visibility with allow-discrete117 (Sep 2023)18 (Sep 2024)Not supportedContent hides as closing starts, so no closing animation
<details name> exclusive group120 (Dec 2023)17.2 (Dec 2023)130 (Sep 2024)Items open independently
Find in page opens details; hidden="until-found"97 (Jan 2022); 102 (May 2022)26.2 (Dec 2025), match not scrolled into view148 (Feb 2026)Closed content is not searchable; until-found acts as hidden

Respect reduced motion

An accordion’s slide is not essential motion: the content arrives either way, so the right response to prefers-reduced-motion is to remove the height animation altogether and let panels open at full height in one frame. A short opacity fade on ::details-content is a fair replacement, because it signals that something changed without moving anything across the screen.

@media (prefers-reduced-motion: reduce) {
  .ac-grid-panel {
    transition: none;
  }
  .ac-details::details-content {
    transition: opacity 0.2s ease;
  }
}

These rules sit at the end of the style sheet so they beat the transition inside @supports, which has the same specificity. content-visibility leaves the list too: with the height snapping to zero there is nothing left to fade on closing.

Frequently asked questions

Can I transition height to auto in CSS now?

In Chrome and Edge 129 and later, yes: set interpolate-size: allow-keywords on :root, or write the open height as calc-size(auto, size), and a transition from height: 0 to height: auto animates. Firefox and Safari did not support either feature as of October 2026, so they jump straight to the final height. For an animation that works everywhere today, transition grid-template-rows from 0fr to 1fr.

Why does my grid-template-rows accordion not collapse fully?

The grid item inside the animated container keeps a minimum height equal to its content. Give that inner wrapper overflow: hidden and min-height: 0, and move any padding onto an element inside it, because padding on the wrapper itself stays visible when the row is 0fr.

How do I make only one details element open at a time?

Give every details element in the group the same name attribute, such as name="faq". Opening one closes the others, with no JavaScript. It works in Chrome and Edge 120, Safari 17.2 and Firefox 130 onwards; older browsers ignore it and let each item open independently.

Should accordions animate when prefers-reduced-motion is set?

Not their height. Content that opens instantly loses nothing, so remove the height or grid-template-rows transition inside a prefers-reduced-motion: reduce media query. A brief opacity fade is an acceptable replacement because it signals the change without moving anything across the screen.