Sticking Elements to Other Elements
Positioning one UI element relative to another is everywhere on the web. Tooltips need to appear above their triggers, and dropdown menus descend from their parent buttons. The idea feels simple, but the implementation details are messy.
What happens when a tooltip is supposed to appear above its button, but that button sits at the very top of the viewport? Traditional CSS has no way to handle that gracefully. We've historically coded around this with JavaScript, and that logic gets heavy fast.
The modern answer comes from the Anchor Positioning API, a browser feature that handles anchor-to-target relationships natively. The API is dense, but a small subset covers most practical scenarios.
Two Elements and a Grid
Anchor positioning revolves around a pair of elements:
- Anchor element — the base element you want to attach something to.
- Target element — the floating element that gets attached to the anchor.
The target needs position: absolute or position: fixed because the API extends the positioned layout mode. Instead of anchoring to the containing block's edges like normal absolute positioning, you can anchor to another element entirely.
- Give the anchor a unique name with the
anchor-nameproperty. Anchor names use the dashed-ident type (like--my-anchor) and look like custom properties, give the target the anchor name throughposition-anchor. - The target needs
position-anchorto reference that anchor. - Every target needs at least one
position-areavalue, or it won't anchor to anything.
Using position-area is like layering an invisible 3×3 grid over the containing block with the anchor at the center. The target can place itself in any of the nine cells with position-area:
position-area: top spreads the target across all columns; specify both axes (e.g., position-area: top left) for a single cell. top span-all is the explicit form of a single axis only.
<style> .anchor { anchor-name: --example-anchor; } .target { position: absolute; position-anchor: --example-anchor; position-area: top; }</style><button class="anchor"> Example button</button><div class="target"> This tooltip’s layout is calculated with the Anchor Positioning API!</div>
Dynamic Behavior and Overflow
The target sticks tight against the anchor by default. There's no built-in gap, but margin still adds space between them. Size the target with max-width instead of a fixed width to let it shrink fluidly with the viewport.
<style> .anchor { anchor-name: --example-anchor; } .target { position: absolute; position-anchor: --example-anchor; position-area: top; /* 👇 Add 8px gap using margin: */ margin-bottom: 8px; }</style><button class="anchor"> Example button</button><div class="target"> This tooltip has been shifted up slightly using margin!</div>
True behavioral power shows when space runs out. When the anchor's near the top of the viewport, the target needs to flip below it. That's handled by overflow detection and fallback position-area values:
/* fallback position */
position-try-fallbacks: bottom;
The browser watches for overflow continually; when detected, you can offer candidate positions. When the target no longer overflows at the fallback, the flip happens. This list can chain candidates with a comma to ensure a fit somewhere in the viewport.
<style> .anchor { anchor-name: --example-anchor; } .target { /* Switch from “absolute” to “fixed”: */ position: fixed; position-anchor: --example-anchor; position-area: top; /* Specify a fallback area: */ position-try-fallbacks: bottom; }</style><button class="anchor"> Example button</button><div class="target"> This tooltip’s layout is calculated with the Anchor Positioning API!</div><p> 👇 Scroll down 👇</p>
Flipping the Caret: Level 2 Container Queries
Here's the bigger gap in the original API. Detecting which position-area is active during a fallback was impossible without reaching for JavaScript.
The Level 2 answer is an anchored container query. Chromium-only as of July 2026 (but with targeting for broad vendor adoption through Interop 2026), the solution requires structure tweaks: the anchor's parent element becomes a container you query against.
Rework the DOM so the target sits inside a .target parent configured as an anchored container handling all positioning logic. The actual visible tooltip style is on the child, which can react to container changes via @container.
Keeping the target parent also handles cosmetics like the caret direction based on its fallback state:
<style> .anchor { anchor-name: --example-anchor; } .target { container-type: anchored; position: fixed; position-anchor: --example-anchor; position-area: top; position-try-fallbacks: bottom; } .tooltip { /* Default styles, for when the target is above the anchor: */ padding: 16px 16px 24px 16px; margin-bottom: 8px; border-shape: var(--downwards-caret); /* Alternative styles for when the target is below the anchor: */ @container anchored(fallback: bottom) { padding: 24px 16px 16px 16px; margin-top: 8px; border-shape: var(--upwards-caret); } /* Fallback styles for browsers that don’t support “border-shape”: */ @supports not ( border-shape: shape(from 0 0, hline to 100%) ) { clip-path: var(--downwards-caret); @container anchored(fallback: bottom) { clip-path: var(--upwards-caret); } } }</style><button class="anchor"> Example button</button><div class="target"> <div class="tooltip"> This tooltip’s layout is calculated with the Anchor Positioning API! </div></div><p> 👇 Scroll down 👇</p>
The flip-block Shortcut
The shorthand keyword flip-block swaps values to the opposite side along the block axis — vertical for conventional writing-modes. It's not a container-query substitute. flip-block cascades to run alongside fallback positions and comes through for edge styling:
Moving margins into the .target is safe because flipping handles spacing reversal in the fallback. Other placement logic is automatic:
.target {
position: fixed;
position-anchor: --example-anchor;
position-area: top;
position-try-fallbacks: flip-block;
}
flip-block is supported across major browsers today — unlike anchored container queries — and often cancels out caret logic with less effort. For visual flip states you still need an anchored container query in Chromium-only cases while waiting for vendor adoption.
.target {
position: fixed;
position-anchor: --example-anchor;
position-area: top;
position-try-fallbacks: flip-block;
/*
Will automatically switch to top margin when the
target is under the anchor:
*/
margin-bottom: 8px;
}
Progressive Enhancement and Older Browsers
Before dropping in this API, know the trade-offs:
- Virtual DOM sanitizers add speed and momentum, but real HTML lives longer and moves slower.
- Polyfills are also an option. Oddbird maintains one handling Level 1 core needs with several rough edges — known and documented. This fallback remains useful until features stabilize.
- Feature queries enable full graceful fallback — if the API is unsupported, render a simple definition list:
.anchor {
anchor-name: --example-anchor;
}
.target {
position: fixed;
/* Fallback experience: Stick to the top of the viewport */
top: 0;
/*
Improved experience, using anchor positioning, but without
a visual caret:
*/
@supports (position-area: top) {
top: revert;
position-anchor: --example-anchor;
position-area: top;
position-try-fallbacks: flip-block;
margin-bottom: 8px;
}
}
/*
The ideal experience, using Anchor Positioning level 2.
Adds the top/bottom caret, flipped using container queries.
*/
@supports (container-type: anchored) {
.target {
container-type: anchored;
}
.tooltip {
padding: 16px 16px 24px 16px;
border-shape: var(--downwards-caret);
@container anchored(fallback: bottom) {
padding: 24px 16px 16px 16px;
border-shape: var(--upwards-caret);
}
}
}
Whether that fallback experience is sufficient depends on the feature itself. There's nothing defensive about leaning on existing JS tooling while actors in the API settle.
What’s certain is that the capacity to do this natively is shaping up. Browsers are closing gaps not yet equal across engines; that gap should close to the good.
Going Deeper with the API
The Anchor Positioning API covers much more ground than what this introduction can fit. For a more thorough treatment of the topic, these external references are worth checking out:
- The CSS Tricks Anchor Positioning Guide by Juan Diego Rodríguez.
- The introduction on web.dev by Una Kravets.
- The official CSSWG specification, along with the Level 2 draft that introduces anchored container queries.
- Anchoreum, an educational game designed to build familiarity with the API through practice.
For those who prefer structured learning, Josh Comeau offers self-paced courses that explore CSS and web animation in significant depth:



