Anchor positioning lets a resizing grid animate its reflow
Iris Calderón
Resize an auto-fill grid and the cells jump. You drag the viewport, a track's worth of items slides across, and there are no in-between frames. Nothing to transition. Grid cells are virtual — the tracks get computed, but the cells themselves aren't boxes the layout engine can interpolate a position on. That's the wall.
The bram.us demo walks around it with CSS anchor positioning and a plain transition on inset. No JavaScript. No View Transitions. Interruptible.
The wrapper you'd normally delete
You need a real box to anchor. So each cell gets one, and the visible content lives inside it as an absolutely-positioned child:
<div class="grid">
<div class="cell">
<div class="content">...</div>
</div>
</div>
Two divs per cell. Normally the kind of markup you'd cut. Here the wrapper earns its keep — it becomes the anchor.
.cell {
aspect-ratio: 1;
anchor-name: --a;
anchor-scope: --a;
}
anchor-name marks the wrapper. aspect-ratio: 1 keeps it square as the grid recomputes tracks.
anchor-scope is doing the real work
The interesting bit is anchor-scope. Anchor names are global by default. Put anchor-name: --a on every cell and every target that references --a becomes ambiguous — the resolver picks one anchor, not the one you meant per cell.
anchor-scope: --a scopes that name to the element's subtree. Every cell can reuse the same identifier. No numbering, no data-attributes, no per-cell custom property. Reuse the name, let the scope keep it local.
The positioned content
The .content element is what animates. It's absolutely positioned, anchored to its parent cell, and stretched to fit it:
.content {
position: absolute;
position-anchor: --a;
inset: anchor(inside);
width: 6rem;
height: 6rem;
transition: inset 0.2s ease;
}
inset: anchor(inside) pins all four edges of .content to the anchor's box. transition: inset 0.2s ease gives the browser something to interpolate. When the grid recomputes and the wrapper .cell lands in a new track, its inset values shift, and the content glides from the old anchor rectangle to the new one.
The explicit width and height on .content matter. Without them, the interpolating inset values pull the box wide or squash it flat mid-animation. Fixed dimensions let the shape stay stable so only the position moves.
Why this beats reaching for JavaScript
View Transitions can do reflow choreography too, but they aren't interruptible in the same way. Resize twice quickly and the second run queues behind the first. This is a plain CSS transition. Drag a window edge and the tiles redirect mid-flight to whatever the new layout says.
And the whole thing costs zero script. No ResizeObserver, no FLIP dance, no library.
Where it doesn't work yet
Firefox is the gap. A commenter on the post reports the content jumps straight to its final position there — the transition never runs. Feature-detect and let the layout fall back to the instant reflow it always did, which is still fine:
@supports (anchor-name: --a) and (inset: anchor(inside)) {
/* the animated path */
}
What to try next
Any auto-fill grid with a live count — a photo wall, a tag cloud, a set of cards keyed off a filter — is a candidate. Wrap each cell, name the anchor, scope it, transition inset. The extra div stops feeling wasteful the moment the reflow reads as motion instead of a snap.
Source: bram.us (bram.us)