CSS & layout

CSS anchor positioning: two properties do the wiring

CSS anchor positioning: two properties do the wiring

You have a button and a menu. The menu opens on click. You want it pinned to the button's right edge, and you want it to flip to the other side when the button drifts too close to the viewport. For years that was JavaScript. Read the button's bounding rect on every scroll. Hand pixel offsets to position: absolute. Rewrite the whole thing the first time a parent turns into a scroll container.

Two CSS properties now do the wiring. anchor-name on the thing you're pinning to, position-anchor on the thing you're pinning. Everything else is a value system layered on top.

What actually got wired up

anchor-name takes a dashed-ident. Same shape as a custom property: it starts with two dashes, but it lives in its own namespace.

.anchor {
  anchor-name: --example-anchor;
}

The target references that name. Absolute or fixed positioning is required — the target has to be out of flow before the anchor system takes over placement.

.target {
  position: absolute;
  position-anchor: --example-anchor;
  position-area: top;
}

That's the wiring. No getBoundingClientRect. No manual scroll handler. The browser tracks the anchor and the target follows.

The nine-cell grid

position-area is where the placement happens. Conceptually, the anchor's containing block is cut into a 3×3 grid. Vertical: top, center, bottom. Horizontal: left, center, right. A single keyword picks a whole row or column; two keywords pick a cell.

position-area: top;           /* whole top strip */
position-area: top center;    /* just the top-center cell */
position-area: top span-left; /* top-center and top-left */

One idea per value. No math, no offsets. If you want a tooltip above the anchor and centered, position-area: top center is the sentence.

There's no built-in gap between anchor and target. Margins still do that. Eight pixels of breathing room on a top-placed target reads exactly like it does anywhere else:

margin-bottom: 8px;

When the target would fall off screen

The interesting part is overflow. A dropdown pinned to a button near the bottom of the viewport shouldn't clip. position-try-fallbacks lists alternate placements the browser can try when the primary one doesn't fit.

.target {
  position-area: top;
  position-try-fallbacks: bottom;
}

For the common case of flipping to the other side of the block axis, one keyword does the whole rewrite for you:

position-try-fallbacks: flip-block;

flip-block flips the target to the opposite side and reverses your directional properties automatically. A margin-bottom becomes a margin-top when it flips up. That single keyword replaces a lot of bug-tracker tickets.

Level 2: the anchor becomes a container

Anchored container queries let a target restyle itself based on which placement fired. The target opts in with container-type: anchored, and children query the state.

.target {
  container-type: anchored;
}

.tooltip-inside {
  @container anchored(fallback: bottom) {
    padding: 24px 16px 16px 16px;
  }
}

Same markup, same anchor wiring, different padding after a flip-block. That's how you land a caret that points up on top placement and down after the flip, without cloning the tooltip.

Where support actually is

Level 1 — anchor-name, position-anchor, position-area, position-try-fallbacks — landed across every major engine by January 2026. Chromium had it from mid-2024, WebKit added it in September 2025, and Gecko was the most recent to ship. Global reach is about 81% as of July 2026. Level 2, the anchored container queries, is around 64%.

That gap matters. Feature-detect both, don't assume the two levels move together.

@supports (position-area: top) { /* level 1 path */ }
@supports (container-type: anchored) { /* level 2 path */ }

For the last slice of users still on browsers without the API, the tutorial reaches for a polyfill and a set of fallback styles the polyfill upgrades from. A floating menu that renders as a plain in-flow block on an ancient browser is still a usable menu.

What this unlocks

Tooltips and dropdowns are the loud examples, but the primitive is broader. Popovers pinned to their trigger. Help bubbles that follow a focused input. Contextual menus that survive a scroll container. Anything you were previously reaching for a floating-UI library to build.

The wrapper div can go too. A .tooltip-wrapper around your button, absolute positioning inside it, then a scavenger hunt for the right overflow: hidden ancestor to blame — none of that is required now. The anchor-target relationship is a name, not a DOM contract. Two elements that share --example-anchor can be siblings, cousins, or on opposite sides of the tree.

What I'd try next: pick one JavaScript-driven floating widget in your app, rewrite it with anchor-name, position-anchor and flip-block, and count the lines that disappear. That count is the story.

Source: Josh W. Comeau (joshwcomeau.com)

Related
CSS & layout

Grid Lanes gets a Field Guide — and a fourth layout primitive to argue about

WebKit has published gridlanes.webkit.org, a Field Guide for the new display: grid-lanes layout primitive, with a playground, a cheat sheet and six demos pitting Grid Lanes against Flexbox, Multicolumn and Grid.

June 22, 2026
CSS & layout

Pseudo-classes are states, not events

CSS-Tricks reframes the everyday choice between :hover, :focus-visible, :has() and the matching JavaScript event listeners. The piece also previews the proposed event-trigger syntax in the Animation Triggers spec, which would let CSS bind animations to events instead of states.

June 29, 2026
CSS & layout

Opposing scroll columns with one view() timeline and three keyframe sets

CSS-Tricks walks through making adjacent columns drift in opposite directions as the page scrolls, with no JavaScript. The trick is a per-item animation-timeline: view() and animation-range: entry 0% cover 100%, repeated across three keyframe sets.

June 25, 2026