Animating CSS Grid Layouts with CSS Anchor Positioning

Recording of the animated grid 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 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 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.

~

# Demo

Here is the demo I built to demonstrate this behavior:

See the Pen
Animated Reorganizing Grid with Anchor Positioning (No View Transitions!)
by Bramus (@bramus)
on CodePen.

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

Or, if you want something more fancy (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.

Published by Bramus!

Bramus is a frontend web developer from Belgium, working as a Chrome Developer Relations Engineer at Google. From the moment he discovered view-source at the age of 14 (way back in 1997), he fell in love with the web and has been tinkering with it ever since (more …)

Unless noted otherwise, the contents of this post are licensed under the Creative Commons Attribution 4.0 License and code samples are licensed under the MIT License

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.