Bram.us

Animating CSS Grid Layouts with CSS Anchor Positioning

Read the original on bram.us ↗
https://www.bram.us/wordpress/wp-content/uploads/2026/09/animated-css-grid-2.mp4
Recording of the high-fidelity demo.

At CSS Day (back in June), someone asked me how Brave (the browser) might have built their New Tab Page which features some tiles laid out in a grid. While the grid in itself is nothing spectacular, something special happens with it when you resize the viewport: as the available space changes, the grid nicely reorganizes itself in an animated way. The animations are also interruptible, so no View Transitions seem to be at play.

So, how did they do it? I’m not sure. But if I were to recreate it, I’d use CSS Anchor Positioning with regular CSS Transitions on top.

~

💁‍♂️ Not familiar with Anchor Positioning yet? Check out my post Anchors Aweigh! which explains all you need to know about it (video included!).

~

# The Technique

The effect we are after is this one: as the grid resizes, the grid cells nicely animate to their new position:

https://www.bram.us/wordpress/wp-content/uploads/2026/09/animated-css-grid.mp4
Recording of the animated grid demo.

The trick here is to use CSS Anchor Positioning and anchor the cell’s content to the cell itself. Since you can’t anchor content directly onto a grid’s cell — grid cells are virtual concepts — you need to inject an extra wrapper div that is set to take up the cell’s available space and act as the anchor. Regular CSS transitions are then responsible for nicely animating things as they shift around.

<div class="grid">
	<div class="cell">
		<div class="content">CONTENT</div>
	</div>
	<div class="cell">
		<div class="content">CONTENT</div>
	</div>
	…
</div>
.grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, 6rem);
	gap: 1em;
}

.cell {
	height: auto;
	aspect-ratio: 1;
	
	anchor-scope: --a;
	anchor-name: --a;
}

.content {
	position: absolute;
	position-anchor: --a;
	inset: anchor(inside);
	width: 6rem;
	height: 6rem;

	transition: inset 0.2s ease;
}

There are four key details that make this whole setup click:

  1. Because anchor names are by default global, they need to be scoped to a specific cell’s subtree. anchor-scope: --a; takes care of that, allowing every .cell to reuse the --a identifier without clashing with any other cells.

  2. The aspect-ratio: 1; on the .cells ensures they take up as much height as they are wide.

  3. The transition declaration ensures the inset nicely transitions from one value to the other.

  4. The .content divs are given explicit dimensions (width and height). Without it, the .content divs would stretch and squeeze as they interpolate across the resizing grid.

Unlike with View Transitions, this technique requires absolutely no JavaScript and the animations are perfectly interruptible. If you rapidly resize the viewport back and forth, the tiles don’t get queued up waiting for animation frames or transition promises to finish—they immediately redirect mid-flight toward their newly calculated positions.

Note: This currently does not work in Firefox because it does not do style and layout interleaving for Anchor Positioning. Instead of resolving to interpolable pixel values, it retains the anchor() value at computed value time.

~

# Demo

Here is the demo I built to demonstrate this behavior:

Try resizing the embed or the .container in the demo itself to see the grid rearrange itself.

Or, if you want something more high-fidelity (and that more closely matches the Brave New Tab Page IIRC):

~

# Spread the word

Feel free to reshare one of the following posts on social media to help spread the word:

~

If this post felt familiar:

  • I actually shared a demo on Bluesky about this back in June. I only got to writing this blog post today. Oh well.

  • Chris Coyier covered the approach of my demo on Frontend Masters. In his write-up, he looks back at how Masonry.js famously animated fluid layouts, and shows how my code brings that exact behavior natively to CSS. Definitely go check out his article: Masonry (with Animation) in CSS.

~

🔥 Like what you see? Want to stay in the loop? Here's how:

I can also be found on 𝕏 Twitter and 🐘 Mastodon but only post there sporadically.