CSS
    animationlayoutfrontend

    Animating to height auto using CSS interpolate-size

    height: auto was never a number the browser could interpolate toward, which is exactly why it never animated at all. These two features finally give the engine something concrete to target.

    Editor: Paul RadfordJul 20, 20268 min read

    Paul RadfordFull-Stack Developer & Editor. Paul is a full-stack developer and editor of template.tips. Articles here are AI-drafted and reviewed by Paul, who knows the code well enough to catch what's wrong and cut the hype.

    Animating to height auto using CSS interpolate-size

    Animating to height auto using CSS interpolate-size

    CSS can now animate directly to height: auto without JavaScript, without a max-height guess, and without ResizeObserver gymnastics. The trick is two connected features: the interpolate-size property, which tells the browser that keyword sizing values like auto are allowed to participate in transitions, and the calc-size() function, which gives the animation engine a concrete number to interpolate toward. Together they let an accordion, a dropdown, or a details panel expand and collapse to its real content height using nothing but CSS.

    Why height: auto was unanimatable for a decade

    CSS transitions and animations work by interpolating between two numeric values. height: 40px to height: 120px is easy: the browser just walks through the pixel values in between. But height: auto is not a number, it is an instruction to the layout engine to "figure it out based on content." You can't interpolate between 40px and "figure it out." So browsers simply refused. The transition would either snap instantly or not animate at all.

    The classic workarounds were all compromises:

    • max-height hack: animate max-height from 0 to some large fixed value like 1000px. It works, but the animation timing gets distorted because the browser is easing toward the fake value, not the real content height. A short accordion item snaps open quickly then pauses, because the easing curve is calculated against 1000px, not against the 80px it actually needs.
    • JavaScript measurement: read scrollHeight, set it as an explicit pixel value, transition, then set it back to auto after the transition ends. This works correctly but means every accordion needs a transitionend listener and careful cleanup, and you're fighting layout thrashing if you're not careful about batching reads and writes.
    • Grid trick: animate grid-template-rows from 0fr to 1fr on a wrapping grid container. This is genuinely clever and has decent support, but it's indirect and confuses anyone who has to maintain the code later.

    calc-size() and interpolate-size remove the need for all three.

    How interpolate-size and calc-size actually work

    interpolate-size is a property, usually set globally, that changes how the browser treats "discrete" keyword values like auto, fit-content, and min-content during animation. By default those keywords are non-interpolable. Setting interpolate-size: allow-keywords tells the engine: when you encounter one of these keywords in an animatable property, resolve it to a real length and interpolate normally.

    css
    1/* Opt the whole document into keyword-aware sizing animation */
    2: root {
    3 interpolate-size: allow-keywords;
    4}

    This single declaration is often all you need. Once it's set, a transition like height: 0 to height: auto will actually animate, because the browser calculates what auto resolves to at each keyframe and steps toward it.

    calc-size() is the more surgical tool. It lets you explicitly wrap a keyword in a calculation context so it becomes animatable even without the global switch, and it lets you do arithmetic on intrinsic sizes. The basic form is:

    css
    1/* calc-size(<basis>, <calculation>) */
    2height: calc-size(auto, size);

    The second argument, size, refers to the resolved value of the basis. You can do real math with it:

    css
    1/* Animate to auto height plus a fixed padding buffer */
    2height: calc-size(auto, size + 16px);

    This is genuinely useful when you want a little breathing room, say for a box-shadow or a border that would otherwise get clipped at the exact content edge. According to the MDN documentation for calc-size(), it works with any intrinsic sizing keyword, including min-content, max-content, and fit-content, not just auto.

    Building an accordion that animates to auto height

    Here's a complete, working accordion pattern. No JavaScript for the animation itself, just a checkbox or <details>-style toggle and CSS doing all the sizing work.

    html
    1<!-- Accordion markup using a native details element for accessibility -->
    2<details class="accordion" name="faq-group">
    3 <summary class="accordion__trigger">What is interpolate-size?</summary>
    4 <div class="accordion__panel">
    5 <p>
    6 It's a CSS property that lets keyword values like auto
    7 participate in animations and transitions, instead of being
    8 treated as non-interpolable.
    9 </p>
    10 </div>
    11</details>
    css
    1/* Opt in globally so auto and fit-content become animatable */
    2: root {
    3 interpolate-size: allow-keywords;
    4}
    5
    6.accordion__panel {
    7 overflow: hidden;
    8 height: 0;
    9 transition: height 0.3s ease, opacity 0.3s ease;
    10 opacity: 0;
    11}
    12
    13/* When the details element is open, animate the panel to its
    14 natural content height using calc-size */
    15details[open] .accordion__panel {
    16 height: calc-size(auto, size);
    17 opacity: 1;
    18}
    19
    20/* Respect users who have asked for less motion */
    21@media (prefers-reduced-motion: reduce) {
    22 .accordion__panel {
    23 transition: none;
    24 }
    25}

    A couple of implementation notes worth calling out. First, overflow: hidden on the panel is still required, exactly as it was with the old max-height hack, because during the animation the height is a partial value and you don't want content spilling out. Second, <details> doesn't natively transition its open/closed state, so this pattern relies on styling the inner .accordion__panel rather than trying to animate the <details> element's own box, which browsers still treat as a hard show/hide toggle in most engines.

    If you'd rather build this with a button and ARIA attributes instead of native <details>, the CSS is identical, you just swap the selector:

    css
    1/* Same technique, driven by a class instead of the open attribute */
    2.dropdown__panel {
    3 overflow: hidden;
    4 height: 0;
    5 transition: height 0.25s ease;
    6}
    7
    8.dropdown__panel.is-open {
    9 height: calc-size(auto, size);
    10}
    js
    1// Minimal JS: just toggles a class and manages aria-expanded
    2const trigger = document.querySelector('.dropdown__trigger');
    3const panel = document.querySelector('.dropdown__panel');
    4
    5trigger.addEventListener('click', () => {
    6 const isOpen = panel.classList.toggle('is-open');
    7 trigger.setAttribute('aria-expanded', String(isOpen));
    8});

    Notice the JavaScript here does zero measurement. It never reads scrollHeight, never sets an explicit pixel value, and never listens for transitionend to reset anything. That's the entire point of the feature: the layout math that used to require JS now lives in the CSS engine.

    Is animating height actually bad for performance?

    Yes, and it's worth being honest about that trade-off rather than pretending calc-size() makes it free. Animating height is a layout property. Every frame of the animation, the browser has to recompute geometry for the animating element and, depending on your document structure, potentially for surrounding elements too (this is the classic "reflow" or "layout" cost). Compare that to animating transform: scaleY() or opacity, both of which the browser can typically handle on the compositor thread without touching layout or paint at all.

    In practice, for a single accordion panel or dropdown, the layout cost is small enough that you won't notice it on any reasonably modern device. The problem shows up when:

    • You're animating height on an element with many siblings that reflow in response (a long list where one item expands and pushes fifty items below it down the page every frame).
    • The animating element contains complex content that itself has expensive layout, like a data table with many columns, or nested flex/grid layouts that recalculate on every size change.
    • You're running several of these animations simultaneously, such as expanding multiple accordion items in a staggered sequence.

    If you're building a single FAQ accordion or a settings dropdown, don't overthink this. Real layout thrashing problems come from doing this at scale, not from one panel opening. If you are animating height inside a virtualized or very long list, it's worth testing on a throttled CPU in DevTools before shipping, and considering whether a transform-based fake (scaling a wrapper, then snapping the real height at the end) is worth the added complexity for that specific case.

    What's the browser support story right now?

    This is the part that determines whether you can ship this today or need a fallback plan. As of early 2026, interpolate-size and calc-size() ship in Chrome and Edge from version 129, and in other Chromium-based browsers on the same engine version. Safari and Firefox have not shipped either feature yet, though both are tracked as active proposals in the CSS Values and Units specification. Check current status on caniuse.com before you commit, since support tables move faster than articles get updated, and confirm the exact behavior against the MDN interpolate-size reference, which stays current with shipping engine versions.

    That gap matters for anything shipping to a general audience today. The good news is that this is one of the cleanest progressive enhancement stories in recent CSS history, because unsupported browsers don't error, they just ignore the animation and snap instantly between states. Your accordion still works, it just isn't smooth in Safari or Firefox until those engines catch up.

    css
    1/* Feature-detect and layer the enhancement on top of a working baseline */
    2.accordion__panel {
    3 overflow: hidden;
    4 height: 0;
    5}
    6
    7details[open] .accordion__panel {
    8 height: auto; /* Safari/Firefox: instant, no animation, still correct */
    9}
    10
    11@supports (interpolate-size: allow-keywords) {
    12 : root {
    13 interpolate-size: allow-keywords;
    14 }
    15
    16 details[open] .accordion__panel {
    17 height: calc-size(auto, size);
    18 transition: height 0.3s ease;
    19 }
    20}

    Wrapping the enhancement in @supports means Safari and Firefox users get an instant, correct, fully functional accordion with no visual glitch. Chrome and Edge users get the smooth animated version. Nobody gets a broken layout, which is the bar you should hold any progressive enhancement to.

    Where this leaves you

    If you're targeting a Chromium-heavy environment (an internal admin tool, an Electron app, a Chrome extension), ship calc-size() today and don't bother with a JS fallback at all. For public-facing sites with a broader audience, the @supports pattern above gives you a real animation where it's available and a correct, instant fallback everywhere else, which is a genuinely better trade-off than maintaining a JavaScript height-measurement library just to support two more browsers.

    The bigger shift here is conceptual: for years, "animate to auto" was the canonical example of something CSS couldn't do, and it justified a small ecosystem of JS libraries built around measuring and resetting heights. That justification is now gone for a growing share of real-world traffic, and it's worth ripping those libraries out of new projects as support widens rather than defaulting to them out of habit.

    Related Articles