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:
-
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.cellto reuse the--aidentifier without clashing with any other cells. -
The
aspect-ratio: 1;on the.cells ensures they take up as much height as they are wide. -
The
transitiondeclaration ensures theinsetnicely transitions from one value to the other. -
The
.contentdivs are given explicit dimensions (widthandheight). Without it, the.contentdivs 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.