Animate height: auto in CSS: interpolate-size or Grid

CSS can animate height: auto, but only in Chromium. Set interpolate-size: allow-keywords on :root and a transition from height: 0 to height: auto interpolates instead of snapping at the end. Firefox and Safari have not shipped it, so anything that has to open smoothly in those engines still needs the grid fallback below.

Chrome 129 shipped interpolate-size and calc-size() in September 2024. Two years later the Mozilla and WebKit tickets are both open and unassigned, and MDN still files both features under limited availability. That is the whole decision. One technique is a single inherited property that does nothing outside Chromium, and the other is an extra wrapper element that animates in every engine.

Why does height: 0 to auto not transition already?

A transition interpolates between two computed values of the same type. auto is a keyword, not a length, so there is nothing to interpolate and the property flips at the end of the duration. Browsers could have relaxed that years ago. They did not, because too much existing CSS assumes intrinsic sizes never animate: MDN’s note is that enabling the behaviour by default “would cause several backwards-compatibility issues”.

So it ships as an opt-in property instead.

How does interpolate-size: allow-keywords work?

interpolate-size is inherited, which is why the documented way to turn it on is once, at the root.

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

.panel {
  height: 0;
  overflow: clip;
  transition: height 300ms ease;
}

.panel[data-open] {
  height: auto;
}

The keywords it covers are auto, min-content, max-content, fit-content and content. One end of the transition still has to be a length or a percentage. You cannot animate min-content to max-content, and asking for it gets you the old discrete flip back.

overflow: clip is not decoration. While the height is mid-transition the content is taller than the box, and without clipping it spills over whatever sits underneath.

When do I need calc-size() instead?

When the target size is the intrinsic size plus something.

.panel[data-open] {
  height: calc-size(auto, size + 2rem);
}

Inside calc-size(), size stands for the basis in the first argument, so calc-size(auto, size) passes the resolved auto height through untouched and calc-size(fit-content, size / 2) halves it. Including the function in a value applies interpolate-size: allow-keywords to that selection on its own, so the root property is optional when you use it. One intrinsic keyword per call, and the whole expression has to resolve to a <length-percentage>. MDN’s guidance is to reach for the inherited property first and keep calc-size() for the cases that genuinely need arithmetic.

Which browsers animate to height: auto?

Chrome and Edge 129 and up, plus the rest of Chromium. Nothing else, as of September 2026.

Firefox tracks it as bug 1945962, status NEW and unassigned, blocked on its own calc-size() implementation in bug 1896734. WebKit’s bug 295132 was filed in June 2025 and has one comment on it, from the bot that mirrors bugs into Apple’s internal tracker. Neither has a target milestone. Treat any timeline you read into that as a guess.

The failure mode is mild, which is what makes the enhancement worth shipping anyway: a browser without support jumps the panel to its open height immediately. The widget still works, it just does not glide. Wrap it in @supports (interpolate-size: allow-keywords) when you want a different rule set for the two cases.

What animates open in Firefox and Safari?

A grid container with one row, transitioned from 0fr to 1fr.

.panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 300ms ease;
}

.panel[data-open] {
  grid-template-rows: 1fr;
}

.panel > .panel-inner {
  overflow: hidden;
  min-height: 0;
}

Nothing here is experimental. The CSS Grid spec gives grid-template-rows an animation type of “if the list lengths match, by computed value type per item in the computed track list; discrete otherwise”. Both sides of this transition are a list of one flexible track, so the lengths match, the track size interpolates, and the row grows with whatever is inside it.

The overflow: hidden on the child is easy to leave out, and leaving it out is why the panel sometimes refuses to close at all. A grid item’s automatic minimum size is content-based when, among other conditions, “its computed overflow is not a scrollable overflow value”. Keep the overflow visible and the row will not shrink below the content’s height, so 0fr resolves to the full height and nothing moves. min-height: 0 sets that minimum explicitly and does the same job.

Two costs. You need the extra inner element, and any padding or border has to live on that inner element, because padding on the grid item itself never collapses and leaves a visible stub when closed.

The max-height guess is the third option and I would not ship it. You pick a number larger than the content will ever be, the browser spends the duration travelling a distance the content does not occupy, and the close reads as a pause followed by a snap. Guess too low and you clip real content.

How do I animate a details element?

Use ::details-content, which reached Baseline in September 2025 and styles the disclosed part of a <details> without a wrapper div. Chrome’s styling guide publishes the pattern with both branches:

::details-content {
  transition: height 0.5s ease, content-visibility 0.5s ease allow-discrete;
  height: 0;
  overflow: clip;
}

@supports (interpolate-size: allow-keywords) {
  :root {
    interpolate-size: allow-keywords;
  }

  [open]::details-content {
    height: auto;
  }
}

@supports not (interpolate-size: allow-keywords) {
  [open]::details-content {
    height: 150px;
    overflow-y: scroll;
  }
}

allow-discrete is doing separate work here. content-visibility is a discrete property, so without it the content vanishes halfway through the close and you animate an empty box. The same rule and its ordering traps come up in our write-up on animating an element out of display: none, and the property itself in the content-visibility: auto post.

One gotcha from the same Chrome article: ::details-content is display: block in the UA stylesheet from Chrome 131, where it used to be display: contents. Disclosed content that relied on height: 100% can break on that change alone.

What does this cost per frame?

Both techniques animate layout. height and grid-template-rows change the size of a box, so the browser re-runs layout for that subtree on every frame, which is the work that web.dev tells you to avoid when it says to “restrict animations to opacity and transform to keep animations on the compositing stage”.

For a FAQ row or a filter panel that is fine. For a 300-row table inside the panel it is not, and a mid-range Android will find that out before your laptop does. If you are chasing an interaction that feels heavy, measure first: our guide on diagnosing INP covers finding which frames the time actually goes to.

What I would ship

For a disclosure widget that has to behave identically everywhere, the grid technique, today. It costs one wrapper element and it has no support story to explain.

For <details> elements, the Chrome pattern above: the cross-browser rules outside the @supports blocks, the smooth height inside them, and a fixed height with a scrollbar in the branch that has no support.

Reach for interpolate-size on its own when the animation is a garnish, the component is awkward to add markup to, and a Safari user seeing an instant open costs you nothing. Revisit when Bugzilla 1945962 moves, not before.

Whoooop builds front ends that get checked in all three engines, not only the one on the developer’s machine. If a site of yours animates badly or scores poorly on Core Web Vitals, our site speed work usually starts by finding which of those animations is doing layout.

Need this built properly?

Whoooop Ltd has spent 15+ years building and maintaining web applications in TypeScript, React, Node.js and serverless — the same ground this post covers.

Get in touch