How to Animate Height Changes with CSS Transitions
Animate a panel from `height: 0` to `height: auto` with CSS, understand browser support, and choose a fallback when intrinsic-size interpolation is unavailable.
To transition a content panel between a collapsed height and its natural content height, opt into intrinsic-size interpolation with interpolate-size: allow-keywords. Then transition height from a length such as 0 to auto:
.panel {
height: 0;
overflow: hidden;
interpolate-size: allow-keywords;
transition: height 250ms ease;
}
.panel.is-open {
height: auto;
}
This is a CSS-first solution, but support is not universal. MDN currently marks interpolate-size as limited availability and not Baseline, so check the browsers and versions your application supports. If you need older-browser support, use a measured-height fallback or a different reveal effect, and test it with your actual content.
1. Understand what the browser is interpolating
Historically, CSS could transition between numeric lengths such as 0px and 240px, but not between a length and an intrinsic sizing keyword such as auto. The browser cannot interpolate between those unlike value types without opting in.
interpolate-size: allow-keywords enables interpolation between a <length-percentage> value and an intrinsic size keyword. One endpoint must still be a length or percentage. It does not enable interpolation between two different intrinsic keywords, such as min-content and max-content. See the MDN reference for interpolate-size.
The property is inherited. Put it on the component when only that component needs the behavior. Putting it on a broader ancestor also opts descendants in, which may be broader than intended.
2. Build a working disclosure panel
The CSS handles the visual transition; your application must change the open state. This complete example uses a button, exposes its state to assistive technology, and keeps the panel in the document:
<button
type="button"
id="details-toggle"
aria-expanded="false"
aria-controls="details-panel"
>
Show details
</button>
<div id="details-panel" class="panel">
<p>This panel grows to fit its content. Add or change content here.</p>
</div>
<style>
.panel {
height: 0;
overflow: hidden;
interpolate-size: allow-keywords;
transition: height 250ms ease;
}
.panel.is-open {
height: auto;
}
@media (prefers-reduced-motion: reduce) {
.panel {
transition-duration: 0.01ms;
}
}
</style>
<script>
const button = document.querySelector('#details-toggle');
const panel = document.querySelector('#details-panel');
button.addEventListener('click', () => {
const isOpen = button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', String(!isOpen));
button.textContent = isOpen ? 'Show details' : 'Hide details';
panel.classList.toggle('is-open', !isOpen);
});
</script>
The media query reduces the motion for users who request reduced motion. Choose a duration and easing curve that suit your interface; the example’s 250 milliseconds is a design choice, not a required value.
3. Check browser support and choose a fallback
MDN marks both interpolate-size and calc-size() as limited availability and not Baseline. Check the compatibility tables for the exact browsers and versions that matter to your users: interpolate-size compatibility and calc-size() compatibility.
Chrome for Developers describes the feature as an opt-in and documents Chrome 130 support for intrinsic sizing keywords including auto, min-content, max-content, and fit-content. That is Chrome-specific guidance; do not assume other browsers have identical support. See Chrome’s guide to animating to intrinsic sizing keywords.
For browsers without support, the CSS above still sets the open state to auto, but the transition may not animate. Choose a fallback based on how the component behaves:
- Measured pixel height: Application code can measure the content and transition between explicit pixel heights. Recalculate when content or available width changes, and handle interrupted transitions. This is appropriate when a smooth height animation and movement of neighboring content are both important.
- Different visual reveal: A transform or opacity effect may suit a component where neighboring content does not need to move with the expanding box. These effects do not reproduce natural document flow in the same way as animating height.
- No animation: If animation is not essential, show or hide the content immediately in unsupported browsers. This avoids extra fallback code and still preserves the interaction.
There is no single fallback recipe that is right for every component. Test your chosen behavior with long content, dynamic content, viewport resizing, and a user toggling the panel before an earlier transition finishes.
4. When to use calc-size()
Use calc-size() when the destination needs arithmetic based on intrinsic size. For example, to make the destination two rem taller than the content’s natural height:
.panel.is-open {
height: calc-size(auto, size + 2rem);
}
Here, size represents the intrinsic size used by the calculation. Regular calc() does not handle intrinsic keywords for this purpose. For a plain transition to auto, interpolate-size is generally the simpler, preferred choice. Details are in the MDN calc-size() reference.
5. Choose the right property for the layout
Animating height changes the element’s box and can require layout work as it changes. That is useful when content below the panel should move down smoothly as the panel opens. It may be unnecessary when you only need an element to appear visually larger or reveal content inside a fixed layout.
MDN cautions against animating box-model properties where possible. If a transform can achieve the intended effect, it may avoid animating the box’s layout size; however, a transform does not cause following document content to reflow as the height changes. Choose based on the visual and layout result, not on a blanket rule that one property is always faster. See the performance notes in the MDN interpolate-size reference.
6. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| The panel jumps open or closed without animating. | The browser does not support intrinsic-size interpolation, or the opt-in is missing. | Check browser support and ensure interpolate-size: allow-keywords applies to the transitioning element. Use a measured-height fallback or accept an immediate state change in unsupported browsers. |
| The panel stays closed. | The open class or state is not being applied, or another selector overrides the open height. | Inspect the computed height, verify the state toggle, and check for competing declarations or higher-specificity rules. |
| The panel’s content is clipped when open. | A fixed height, max-height, or another overflow rule limits the content. |
Remove the conflicting size constraint and confirm the open state resolves to the intended intrinsic size. |
| The animation works in one browser but not another. | Support differs across browser engines and versions. | Consult the current compatibility tables for your target set and test in those browsers. Do not rely on a Chrome version as proof of support elsewhere. |
| Nested elements unexpectedly interpolate intrinsic sizes. | interpolate-size is inherited from an ancestor. |
Scope the opt-in to the component instead of a broad ancestor, or review the descendants that inherit it. |
| The animation feels sluggish or causes surrounding layout work. | Changing height affects layout, and neighboring content must be repositioned. | Keep the animated subtree as simple as practical. If surrounding content does not need to move, consider a visual reveal using a transform or opacity instead. |
| Rapid toggles leave a measured-height fallback at the wrong size. | The code measured a stale size or did not account for an in-progress transition or changed content. | Re-measure when content or width changes, and make the fallback handle interrupted transitions before relying on it. |
7. Performance, reliability, and cost
This CSS technique has no API or service cost. Its main trade-off is layout work: an animated height changes layout over time, which is often exactly what an accordion needs because following content moves with it. Keep the implementation scoped, avoid unnecessary simultaneous layout animations, and check the result on representative content and devices. No single timing or performance figure applies to every page.
For reliability, test both state changes, reduced-motion behavior, browser support, and content that changes after opening. If a panel can load images or other content after it opens, verify that its final height remains correct and that the selected fallback recalculates as needed.
8. Or skip the browser setup
If you need screenshots of the page states for documentation, review, or an automated workflow, ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request can return a PNG, JPEG, WebP, or PDF. Use the same interactive URL you want to capture; arrange the page state in your application or provide suitable capture instructions and timing.
See the ScreenshotNeo API documentation for request options. This cURL example saves a WebP screenshot:
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com/accordion \
-o shot.webp
Python:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com/accordion"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://example.com/accordion'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
- Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot; each step can be turned off.
- Bot checks, blank pages, timeouts, failed loads, and cache hits are never billed. Response headers say the page verdict and billing status.
- An MCP server lets AI agents, including Claude, Cursor, and other MCP clients, take screenshots with
take_screenshot, inspect pages withget_page_info, and capture PDFs withcapture_pdf. - The free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 screenshots; every feature is on every plan.
Sign up free for 1,000 screenshots a month, no card required.
9. FAQ
How do I transition height from 0 to auto?
Set interpolate-size: allow-keywords on the element, transition its height, and change the open state from 0 to auto. Check support for your target browsers.
Can I interpolate from min-content to max-content?
No. The opt-in enables interpolation between a length-percentage and an intrinsic size keyword, not between two different intrinsic keywords.
Should I use calc-size() for every expanding panel?
No. Use it when you need to calculate from an intrinsic size. For a plain transition to auto, use interpolate-size.
Does this require JavaScript?
The CSS transition does not. JavaScript or another state mechanism is needed only if your application uses it to toggle the open state.


