CSS grid-template-rows Animation: Expand and Collapse
Expand and collapse with grid instead of max-height
The max-height trick needed a guess that was big enough for any content, so panels opened too fast or got cut off. Transitioning grid-template-rows from 0fr to 1fr animates to the real height, whatever the content is.
The old hack vs the modern fix
View demo.panel {
max-height: 0;
overflow: hidden;
transition: max-height .3s ease;
}
.panel.open {
max-height: 1000px;
}.panel {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows .3s ease;
}
.panel.open {
grid-template-rows: 1fr;
}
.panel > div {
overflow: hidden;
}<button onclick="this.nextElementSibling.toggleAttribute('data-open')">Toggle</button><div class="grid grid-rows-[0fr] transition-[grid-template-rows] duration-300 data-[open]:grid-rows-[1fr]"> <div class="overflow-hidden">Content</div></div>Grid animation browser support
Live demo
Why the modern way wins
No height guess
The row grows to the content's real size, so there's no max-height: 1000px that's too small for long content and too slow for short content.
No measuring script
You don't read scrollHeight in JavaScript and write a pixel height back. The transition works from CSS alone.
Works everywhere today
Grid track animation is Widely available, so it's a safe pick while interpolate-size is still Chrome and Edge only.
- Lines Saved
- Guess → exact
- No magic number
- Old Approach
- max-height hack
- Guessed big number
- Modern Approach
- grid-template-rows
- 0fr to 1fr
How it works
A grid track sized 0fr takes none of the free space, and 1fr takes all of it. Browsers can interpolate between the two, so a one-row grid that switches from 0fr to 1fr animates from nothing to exactly the height of its content.
The child needs overflow: hidden, and in some layouts min-height: 0, so its content doesn't spill out while the row is still small. Padding belongs on an inner element, because padding on the child itself keeps a sliver showing when the row is at 0fr.
The old max-height trick animated toward a number like 1000px, so a short panel finished its visible change long before the transition ended, and very long content got clipped. The grid version always runs the full duration over the real height.