CSS Smooth Height Transition to Auto: The Complete Guide
Learn how to animate height to auto in CSS using the new interpolate-size and calc-size() properties, plus proven workarounds for older browsers.

CSS Smooth Height Transition to Auto: The Complete Guide
Animating an element's height from 0 to auto is one of the oldest unsolved problems in CSS. Developers have been fighting this limitation for over a decade, resorting to JavaScript measurements, arbitrary max-height values, and other creative hacks. The core issue is simple: CSS transitions need a concrete start value and a concrete end value, and auto is not concrete. The browser does not know how to interpolate between 0px and "whatever the content needs."
That era is finally ending. With the arrival of interpolate-size and calc-size(), browsers are gaining native support for smooth CSS height transition to auto without a single line of JavaScript. This guide covers every approach -- the old workarounds you may still need, the new native solution you should adopt, and practical component patterns you can drop into production today.
If you are sharpening your CSS skills for competitive challenges on StyleWars, understanding transitions at this level is exactly the kind of deep knowledge that separates good solutions from great ones.
Why Height Auto Cannot Be Transitioned (The Classic Problem)
The CSS transition specification requires that both the start and end values of an animated property be interpolable -- meaning the browser must be able to calculate intermediate values between them. When you write:
.panel {
height: 0;
overflow: hidden;
transition: height 0.3s ease;
}
.panel.open {
height: auto;
}
The browser sees 0px as the start value and auto as the end value. It cannot calculate what "30% of the way between 0px and auto" looks like because auto is a keyword, not a number. The result is an instant jump with no animation at all.
This is not a bug. The specification explicitly states that auto is a non-interpolable value for dimensional properties. The browser resolves auto to a computed pixel value only during layout, and that computed value is not exposed to the transition engine in time to produce intermediate frames.
Understanding this constraint is essential. Every workaround exists to give the browser a numeric value it can interpolate, and the new native solution works by explicitly telling the browser that keyword values should be resolved before interpolation begins.
The Old Workarounds
Before reaching for the modern solution, it is worth understanding the classic hacks. You will encounter them in legacy codebases, and some remain the best option when you need to support older browsers.
The max-height Hack
The most common workaround is to transition max-height instead of height. Since max-height accepts numeric values, you set it to a value larger than the content will ever need:
.panel {
max-height: 0;
overflow: hidden;
transition: max-height 0.4s ease;
}
.panel.open {
max-height: 500px; /* Must be larger than actual content */
}
This works, but it has real problems. The transition duration is calculated against the full max-height value, not the actual content height. If your content is 120px tall but max-height is 500px, the visible expansion finishes in roughly the first quarter of the transition duration, and the remaining time is spent "animating" through empty space. The collapse feels even worse -- there is a visible delay before the element starts shrinking because the animation begins from 500px, not from the actual content height.
You can mitigate this by choosing a max-height close to the expected content height, but that makes the solution fragile. Content changes, translations expand text, and suddenly your "safe" value is too small.
The transform: scaleY Approach
Another approach uses transform: scaleY() to visually collapse the element:
.panel {
transform: scaleY(1);
transform-origin: top;
transition: transform 0.3s ease;
}
.panel.collapsed {
transform: scaleY(0);
}
The animation is smooth and runs on the compositor thread, making it very performant. However, scaleY does not remove the element from the layout flow. The space the element occupies remains, and surrounding content does not reflow. You end up with invisible gaps or overlapping elements. This approach works only in specific visual contexts where layout disruption is acceptable.
The Grid Row Trick
A more elegant workaround uses CSS Grid's grid-template-rows property:
.accordion-item {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 0.35s ease;
}
.accordion-item.open {
grid-template-rows: 1fr;
}
.accordion-item > .content {
overflow: hidden;
}
This technique transitions from 0fr to 1fr, and the browser can interpolate fractional unit values. The content wrapper collapses to zero height when the row is 0fr and expands to its natural height when the row is 1fr. It handles dynamic content correctly and the timing feels natural because the animation maps directly to the actual content height.
The grid row trick is the best of the old workarounds. It produces correct timing, works with dynamic content, and requires no JavaScript. The main drawback is that it forces a grid layout context, which may not always be desirable, and it requires a wrapper element around the content.
For a deeper look at techniques like this that reduce the code you write, see our guide on CSS tips and tricks to write less code.
The Native Solution: interpolate-size and calc-size()
CSS now has first-class support for transitioning to and from intrinsic sizing keywords like auto, min-content, max-content, and fit-content. This comes in two parts: a global opt-in property and a new function.
interpolate-size: allow-keywords
The interpolate-size property tells the browser that intrinsic sizing keywords should be resolved to their computed pixel values before interpolation. This is the simplest way to enable smooth height animation in CSS:
:root {
interpolate-size: allow-keywords;
}
.panel {
height: 0;
overflow: hidden;
transition: height 0.35s ease;
}
.panel.open {
height: auto;
}
That is it. By adding a single declaration to :root, every transition involving height: auto (or width: auto, min-height: min-content, and so on) will animate smoothly. The browser resolves auto to its computed value at transition start time and interpolates between the numeric values.
Setting interpolate-size: allow-keywords on :root is the recommended approach for new projects. It is a progressive enhancement -- browsers that do not support it will simply show the instant jump, which is the same behavior developers have lived with for years. No functionality breaks.
The property is inheritable, so you can also scope it to specific subtrees if you prefer a cautious rollout:
.accordion {
interpolate-size: allow-keywords;
}
calc-size(): Fine-Grained Control
For cases where you need to perform calculations involving intrinsic sizes, the calc-size() function provides explicit control. It lets you reference the intrinsic size as a variable and do math with it:
.panel {
height: 0;
overflow: hidden;
transition: height 0.35s ease;
}
.panel.open {
height: calc-size(auto);
}
The above is functionally identical to using interpolate-size: allow-keywords with height: auto, but calc-size() shines when you need arithmetic:
.panel.open {
height: calc-size(auto, size + 2rem);
}
Here, size is a special keyword inside calc-size() that refers to the resolved intrinsic value. This example makes the panel 2rem taller than its natural content height -- useful for adding breathing room without extra padding that might affect internal layout.
You can also use calc-size() for partial expansions:
.preview {
height: calc-size(auto, size * 0.5);
}
.preview.expanded {
height: calc-size(auto);
}
This creates a preview state that shows exactly half the content, expanding to full height when activated. Try achieving that cleanly with the old workarounds.
When to Use Which
Use interpolate-size: allow-keywords when you want a global opt-in that makes height: auto transitions work everywhere. Use calc-size() when you need to perform calculations with the intrinsic size or when you want to be explicit about which elements participate in keyword interpolation.
Browser Support
As of early 2026, interpolate-size and calc-size() are supported in:
- Chrome / Edge 129+ (stable since September 2024)
- Safari 18.2+ (stable since December 2024)
- Firefox 135+ (stable since February 2025)
That covers all modern evergreen browsers. For projects that still need to support older browser versions, use feature detection:
:root {
interpolate-size: allow-keywords;
}
/* Fallback for browsers without support */
@supports not (interpolate-size: allow-keywords) {
.panel {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 0.35s ease;
}
.panel.open {
grid-template-rows: 1fr;
}
.panel > .content {
overflow: hidden;
}
}
This gives you the best of both worlds: the clean native approach where supported, and the grid row fallback elsewhere.
Practical Examples
Theory is useful, but you need patterns you can copy into real projects. Here are three common components that rely on animating height to auto in CSS.
Accordion
The accordion is the canonical use case. Multiple sections, each with a header that toggles the visibility of its content panel.
<div class="accordion">
<details class="accordion-item" name="faq">
<summary class="accordion-header">What is a CSS battle?</summary>
<div class="accordion-body">
<p>A CSS battle is a timed challenge where you recreate
a target image using only HTML and CSS.</p>
</div>
</details>
<details class="accordion-item" name="faq">
<summary class="accordion-header">How is scoring calculated?</summary>
<div class="accordion-body">
<p>Submissions are scored on visual accuracy and code length.</p>
</div>
</details>
</div>
:root {
interpolate-size: allow-keywords;
}
.accordion-item {
border-bottom: 1px solid #e2e2e2;
}
.accordion-header {
padding: 1rem;
cursor: pointer;
font-weight: 600;
list-style: none;
}
.accordion-header::marker {
content: "";
}
.accordion-body {
height: 0;
overflow: hidden;
padding-inline: 1rem;
transition: height 0.35s ease, padding 0.35s ease;
}
.accordion-item[open] .accordion-body {
height: auto;
padding-block: 0.5rem 1rem;
}
This uses the native <details> element for accessibility and keyboard support out of the box, combined with interpolate-size for smooth animation. No JavaScript required. The name attribute on the <details> elements ensures only one panel is open at a time, giving you exclusive accordion behavior for free.
If you want to learn more about what is possible when you push CSS to its limits, our guide to CSS battles covers the mindset and techniques that competitive CSS demands.
Dropdown Menu
Navigation dropdowns need smooth reveal animations. Here is a pattern using a hover trigger:
<nav class="nav">
<div class="nav-item">
<a href="#" class="nav-link">Products</a>
<div class="dropdown">
<a href="#" class="dropdown-link">Analytics</a>
<a href="#" class="dropdown-link">Automation</a>
<a href="#" class="dropdown-link">Integrations</a>
<a href="#" class="dropdown-link">Security</a>
</div>
</div>
</nav>
:root {
interpolate-size: allow-keywords;
}
.nav-item {
position: relative;
}
.dropdown {
position: absolute;
top: 100%;
left: 0;
min-width: 200px;
background: #fff;
border-radius: 8px;
box-shadow: 0 4px 20px rgb(0 0 0 / 0.12);
height: 0;
overflow: hidden;
opacity: 0;
transition: height 0.3s ease, opacity 0.2s ease;
}
.nav-item:hover .dropdown,
.nav-item:focus-within .dropdown {
height: auto;
opacity: 1;
}
.dropdown-link {
display: block;
padding: 0.6rem 1rem;
color: #333;
text-decoration: none;
}
.dropdown-link:hover {
background: #f5f5f5;
}
The combination of height and opacity transitions creates a polished reveal effect. The focus-within pseudo-class ensures the dropdown remains visible during keyboard navigation.
Expandable Card
Content cards that expand to show additional details are common in dashboards and content feeds:
<div class="card">
<h3 class="card-title">Monthly Performance</h3>
<p class="card-summary">Your CSS accuracy improved by 12% this month.</p>
<div class="card-details">
<p>You completed 34 challenges with an average accuracy
of 98.7%. Your best result was Challenge #89 where you
achieved a perfect 100% match in just 142 characters.</p>
<ul>
<li>Challenges completed: 34</li>
<li>Average accuracy: 98.7%</li>
<li>Best code golf score: 142 chars</li>
</ul>
</div>
<button class="card-toggle" aria-expanded="false"
onclick="this.parentElement.classList.toggle('expanded');
this.setAttribute('aria-expanded',
this.parentElement.classList.contains('expanded'))">
Show details
</button>
</div>
:root {
interpolate-size: allow-keywords;
}
.card {
max-width: 400px;
padding: 1.5rem;
border-radius: 12px;
background: #fff;
box-shadow: 0 2px 12px rgb(0 0 0 / 0.08);
}
.card-title {
margin: 0 0 0.5rem;
}
.card-summary {
color: #555;
}
.card-details {
height: 0;
overflow: hidden;
transition: height 0.4s ease;
}
.card.expanded .card-details {
height: auto;
}
.card-toggle {
margin-top: 0.75rem;
background: none;
border: 1px solid #ccc;
border-radius: 6px;
padding: 0.4rem 1rem;
cursor: pointer;
font-size: 0.9rem;
transition: background 0.2s;
}
.card-toggle:hover {
background: #f0f0f0;
}
The expandable card demonstrates how straightforward the pattern becomes with interpolate-size. The details section goes from height: 0 to height: auto with a single class toggle. The transition timing maps perfectly to the content because the browser resolves auto to the exact pixel height needed.
Performance Considerations
Height transitions trigger layout recalculations on every frame. This is unavoidable -- the browser must reflow the document to compute intermediate heights. For most use cases (accordions, dropdowns, cards), this is perfectly fine. Modern browsers handle these reflows efficiently, especially when the animated element does not affect many siblings.
If you are animating height on dozens of elements simultaneously, consider these optimizations:
- Use
contain: layouton the animated element's parent to limit the scope of reflows. - Avoid animating height on elements with complex subtrees. If a panel contains heavy content, the reflow cost per frame increases.
- Prefer the grid row trick for long lists where many items might animate at once, as
grid-template-rowstransitions can be slightly more efficient in some rendering engines.
For single-element transitions like the examples above, interpolate-size with height: auto performs well and there is no reason to avoid it.
Common Pitfalls
A few issues trip up developers when implementing CSS height transition to auto:
Forgetting overflow: hidden. Without it, content is visible even when height is 0. The transition appears to do nothing because the content was never visually hidden in the first place.
Transitioning padding alongside height. If your element has vertical padding, setting height: 0 does not collapse it fully -- the padding remains. Either transition padding separately or move the padding to an inner wrapper.
Expecting transitions on initial page load. CSS transitions do not fire on the initial style application. If an element starts with height: auto and you want it to animate in on page load, you need a CSS animation (@keyframes) instead of a transition.
Using display: none as the collapsed state. Toggling display between none and block cannot be transitioned smoothly even with interpolate-size. Use height: 0 with overflow: hidden instead. If you need to remove the element from the accessibility tree when collapsed, add visibility: hidden to the collapsed state and transition it alongside height.
Many of these patterns overlap with techniques developers use in CSS challenges to replace JavaScript -- the constraint of working within pure CSS forces you to understand exactly how layout and transitions interact.
Wrapping Up
The interpolate-size: allow-keywords property is the most impactful quality-of-life improvement CSS transitions have received in years. A problem that spawned thousands of Stack Overflow answers, dozens of JavaScript libraries, and countless hours of developer frustration is now solved with a single line of CSS.
For new projects, add interpolate-size: allow-keywords to your :root and write height: auto transitions as if they always worked. For projects supporting older browsers, use the grid row trick as a fallback behind @supports. And for advanced cases where you need arithmetic with intrinsic sizes, reach for calc-size().
The days of guessing max-height values and accepting broken animation timing are over. Write the CSS that should have worked all along -- now it does.
If you are looking for a place to practice these techniques and push your CSS knowledge further, StyleWars gives you a competitive arena where every property and every trick matters. The smoothest transitions win.
Sharpen Your CSS Skills
Put what you learned into practice. Try a CSS battle and see how you compare against other developers on the leaderboard.


