CSS Grid Animation Is Here: What You Need To Know
After a long wait, the CSS grid-template-rows and grid-template-columns properties are now animatable in all major browsers. Grid layouts technically supported animation in the spec for years, but it’s only recently that browser support has caught up. That opens up a lot of layout effects that previously required JavaScript or hacky fallbacks.
Here’s how to push the feature, along with a few gotchas worth keeping in mind.
A Working Two-Column Example
A simple expanding sidebar is a perfect intro. We want a two-column grid where the left column grows on hover — its width should transition smoothly, not jump.
Example structure:
<div class="grid">
<div class="left"></div>
<div class="right"></div>
</div>
Add display: grid to the parent container. Then define the columns with grid-template-columns. The left column starts narrow; auto lets the right column take the remaining space.
.grid {
display: grid;
}
.grid {
display: grid;
grid-template-columns: 48px auto;
}
Add a transition so the width change is gradual:
.grid {
display: grid;
grid-template-columns: 48px auto;
transition: 300ms; /* Change as needed */
}
Two issues need resolving. First, the hover state that triggers the resize needs to apply when the left column is hovered — but grid-template-columns is set on the parent. Second, we need the growth to target that same parent property.
The :has() pseudo-class solves this elegantly. It acts as a parent selector: we can target a parent element based on a child’s state. So, we can match .grid only while its child .left is hovered.
.grid:has(.left:hover) {
/* Hover styles */
}
With that selector, expand the left column on hover — say to 30% — and the right column shrinks to fill the rest:
.grid {
display: grid;
transition: 300ms;
grid-template-columns: 48px auto;
}
.grid:has(.left:hover) {
grid-template-columns: 30% auto;
}
If your project uses CSS variables, cleaner code like this works too:
.grid {
display: grid;
transition: 300ms;
grid-template-columns: var(--left, 48px) auto;
}
.grid:has(.left:hover) {
--left: 30%;
}
That’s roughly nine lines of effective CSS — no JavaScript required for a layout interaction that used to be quite fiddly.
Expanding Panels
The same approach works with multiple columns and content that expands on hover. Transitions can apply to the column widths as well as the background colors of the individual cells, making side-by-side panels visually expand when a user hovers over content.
A useful reminder: transitions from columns declared with repeat() can behave unpredictably in some cases. Set each track individually instead, e.g., grid-template-columns: 1fr 1fr 1fr, to avoid glitchy interpolations.
Adding Rows and Columns
Smoothly introducing a new column into the grid is also possible, but it has a hard constraint: the “new” column must already exist in the grid. You can’t transition from two declared columns into three. Use 0fr to give an existing third column zero space, then transition it up to 1fr:
- Works:
grid-template-columns: 1fr 1fr 0fr→grid-template-columns: 1fr 1fr 1fr - Doesn’t work:
grid-template-columns: 1fr 1fr→grid-template-columns: 1fr 1fr 1fr
That’s unsurprising knowing how transition interpolation behaves — both states need the same track count.
There’s no trick with hidden content, although do note the 0fr still requires the unit to be written despite the value.
Beyond Basic Buttons
For a creative demonstration: a stylized Mondrian-style layout uses grid-row and grid-column together with span to build the blocks, then animates the template properties via @keyframes. Despite looking intricate, it’s no more complex than the examples above — just more tracks and points of motion. Similar patterns also produce nice loading spinner-esque effects as alternate implementations.
Floating panel-style animated grids are another showcase: the layout itself works the same, but specific grid items are placed outside the visible flow behavior, providing interest without making all tracks obvious.



