Stockio

Animate Height Auto in CSS With interpolate-size

Choose a reliable approach to expanding panels: native intrinsic-size interpolation, grid tracks, or measured heights, with accessible controls and reduced-motion fallbacks.

By Aiko Tanaka

Motion Designer ·

On this page

An expanding panel should reveal its contents without guessing how much space the text needs. That becomes harder when translations wrap differently, images arrive late, or users enlarge their fonts. The right solution starts with the panel's behavior, not a convenient pixel value.

Establish the panel contract

Decide whether expansion should push surrounding content, overlay it, or simply reveal an already reserved area. Only the first case requires animating layout height. A transform can make an overlay look larger, but it does not move the content underneath it.

Use the CSS Grid Generator to prototype the panel's outer layout and inspect how neighboring tracks respond. Then reduce the expanding region to a single row before adding motion. Separating those jobs makes the layout contract easier to test.

Use a real button with an accurate expanded state and a relationship to the controlled panel. Decide where keyboard focus goes when the panel closes. A visual collapse must not leave links reachable inside an invisible region. Neither clipping nor a zero-height track removes descendants from keyboard navigation.

Start with an immediate, functional open-and-close interaction. Add animation only after the control works without it. The micro-interaction guide provides useful context for matching feedback to an action rather than adding movement for its own sake.

Prefer intrinsic interpolation when available

The historical difficulty is that a numeric height and an intrinsic keyword are not ordinarily interpolable. The interpolate-size property enables supported transitions between a length and an intrinsic size such as auto. It does not make every pair of intrinsic keywords interpolable.

Scope the opt-in to the component because the property inherits. Check MDN's interpolate-size reference and your actual browser matrix before relying on it. Unsupported browsers should still receive a working state change, just without the interpolation.

.panel {
height: 0;
overflow: hidden;
}
.panel[data-open="true"] {
height: auto;
}
@supports (interpolate-size: allow-keywords) {
.panel {
  interpolate-size: allow-keywords;
  transition: height 220ms ease-out;
}
}
@media (prefers-reduced-motion: reduce) {
.panel { transition: none; }
}

Keep padding on a child rather than the height-controlled wrapper. Otherwise a zero content height can still leave visible padding. Borders can create the same confusing sliver; put them on the surrounding component or handle them as a separate visual state.

This example covers visual geometry, not the complete interaction controller. If you use the hidden attribute, remove it before opening and restore it after closing finishes. Hiding immediately cancels the visible exit. Use inert or equivalent focus management during collapse, and provide an immediate completion path when motion is disabled.

Use a grid row as a progressive fallback

A single-track grid is a useful alternative when intrinsic interpolation is outside your support target. Transition the row between zero and one fractional unit, and allow its child to shrink below its automatic minimum size. Read MDN's grid-template-rows documentation for track sizing details.

.reveal {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 220ms ease-out;
}
.reveal[data-open="true"] {
grid-template-rows: 1fr;
}
.reveal__clip {
min-height: 0;
overflow: hidden;
}
.reveal__content {
padding-block: 1rem;
}
@media (prefers-reduced-motion: reduce) {
.reveal { transition: none; }
}

The clip element is the grid item; the padded content sits inside it. That structure matters. Putting padding on the grid item can prevent complete collapse. Test long unbroken strings, nested flex containers, and images because their intrinsic minimums can expose assumptions hidden by a short paragraph.

Use the Grid versus Flexbox guide for the surrounding layout, but avoid turning the whole page into an animated grid just to reveal one paragraph. Keep the animated region narrow in responsibility and DOM scope.

Grid is not a free compositor animation. Changing track sizes can trigger layout throughout the transition. It is often perfectly acceptable for one modest accordion, but opening dozens of complex cards simultaneously deserves measurement on slower hardware.

Measure only when the behavior requires it

A measured-height transition gives explicit numeric endpoints. On opening, expose the panel for measurement, read its content height, establish the current pixel height, and animate toward the target. After completion, restore auto so future content changes can flow naturally.

On closing, read the current rendered height and freeze that value before requesting zero on a subsequent frame. Writing both endpoints in one rendering turn can cause the browser to coalesce them. Avoid scattered read-write-read sequences across many panels; batch measurements before writes where possible.

Interruption is the difficult part. When a user reverses direction halfway through, begin at the current rendered height, not the last intended endpoint. Cancel stale completion callbacks so an old closing operation cannot hide a newly opened panel. Filter transition events by both target and property.

Treat dynamic content as a separate requirement. Images, validation messages, and streamed text can change the target while movement is underway. A ResizeObserver can detect content changes, but observing and rewriting the same constrained box can create feedback loops. Observe an inner content element and retarget deliberately.

Avoid a huge max-height unless the content genuinely has a known upper bound. If the real panel is 150 pixels tall and the maximum is 1,000 pixels, much of the nominal animation duration occurs after visible movement stops. Closing can also appear delayed. That mismatch makes timing unpredictable across content lengths.

Test motion, semantics, and rendering together

Use reduced motion to remove the size transition, not the ability to expand. The prefers-reduced-motion reference explains how to target the preference. Completion logic must work even when no transition event fires.

Test rapid toggles, opening while images load, resizing mid-transition, and closing while focus is inside. Move focus back to the trigger before making its focused descendants unavailable. Ensure the trigger's state describes the requested open state consistently throughout the animation.

Use browser performance tools to inspect layout and paint during expansion. Height animation costs layout, regardless of whether CSS or JavaScript supplies the endpoint. Avoid permanent will-change declarations as a supposed cure; they do not turn layout-dependent properties into transforms.

Check a long panel near the bottom of the viewport. Moving content can shift the user's reading position or hide the trigger. Shorten the transition or skip it for unusually large disclosures rather than making users wait through a dramatic reveal.

Key takeaways

  • Choose intrinsic interpolation when supported, with immediate state changes as the baseline.
  • Use a zero-to-one grid track when its wrapper structure fits the component.
  • Measure heights only when interruptions and changing content justify the extra controller logic.
  • Keep hidden content out of keyboard navigation throughout closing.
  • Test reduced motion, long content, and rapid reversals before tuning easing.
Featured sponsor

Reach designers & developers at work

Put your product next to the tools people use every day. One sponsor per placement, no tracking pixels.

Become a sponsor

Keep reading

Related articles

More in Motion & Animation