CSS & layout

Anchor positioning lets a resizing grid animate its reflow

Anchor positioning lets a resizing grid animate its reflow

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)

Related
CSS & layout

Animating border-image finally works if you register the gradient stop

Preethi's new CSS-Tricks piece animates a gradient border-image by wrapping one of its stops in a typed custom property. The trick unlocks a moving decorative border in pure CSS, and hits a wall the moment your box has rounded corners.

August 11, 2026
CSS & layout

Gap decorations: column-rule finally leaves multi-column

Chrome and Edge 149 ship CSS gap decorations, extending column-rule to grid and flexbox and adding row-rule for the other axis. The syntax was reworked before landing, so here is what actually changed and what to reach for first.

August 4, 2026
CSS & layout

One grid, five rounds, no track matching

A rebuild of the FIFA knockout layout with CSS subgrid shows how nested rounds can share the parent grid's columns and rows, with negative line numbers doing the trimming.

July 19, 2026