What problem does CSS anchor positioning (`anchor-name`, `position-anchor`, the `anchor()` function) solve for tooltips and popovers, and how would you ship it while support is uneven?
answer
- overlays escape clipping, lose their trigger
- let the layout engine hold the relationship
- name the anchor, point at it, resolve insets
- fallback placements without scroll listeners
- gate it behind @supports
basics
~20 sAnchor positioning lets an absolutely or fixed positioned overlay resolve its offsets from another element's box instead of its containing block, so a tooltip stays attached to its trigger without JavaScript measurement — including when the overlay is in the top layer.
solid answer
~50 sAn overlay that escapes clipping — by going `position: fixed`, into a root container, or into the top layer — loses its natural relationship to the element that triggered it, which is why tooltip libraries measure trigger rectangles in script and rewrite `top`/`left` on every scroll and resize. Anchor positioning moves that into CSS. You name the trigger with `anchor-name: --trigger`, point the overlay at it with `position-anchor: --trigger`, and resolve inset properties from the anchor's box using `anchor()`, for example `top: anchor(bottom)`; `position-area` expresses common placements declaratively, and `@position-try` with `position-try-fallbacks` provides flip-when-it-would-overflow behaviour. The overlay must be absolutely or fixed positioned for `anchor()` to apply. Support is uneven — it shipped first in Chrome 125 in 2024 — so gate it with `@supports (anchor-name: --a)` and keep a workable static placement outside that block.
code
css · 11 lines.trigger {
anchor-name: --menu-trigger;
}
.menu {
position: fixed;
position-anchor: --menu-trigger;
top: anchor(bottom);
left: anchor(left);
min-inline-size: anchor-size(width);
}go deeper
Know the shape of the problem: an overlay that escapes clipping no longer sits next to its trigger, and CSS anchor positioning is the newer way to reconnect them without script.
Explain the three moving parts — anchor-name on the trigger, position-anchor on the overlay, anchor() inside inset properties — and that the overlay must be absolutely or fixed positioned.
Show the progressive-enhancement plan: an @supports gate, a fallback placement the design accepts, and a clear statement of when you would keep an existing JavaScript positioner instead.
Own the adoption decision — support floor, whether two positioning systems may coexist during migration, and how much bespoke overlay code the team retires by moving placement into the layout engine.
## The problem it solves Every escape route for an overlay costs you its relationship to the thing it belongs to. Absolutely positioning a dropdown inside its trigger's wrapper keeps them together but gets it clipped by an ancestor's `overflow` and trapped by a transformed ancestor. Switching to `position: fixed`, mounting into a root overlay container, or promoting into the top layer solves clipping and painting — and now the overlay's offsets are measured from the viewport, which knows nothing about where the trigger is. The industry answer was JavaScript: read the trigger's rectangle, compute a placement, write inline `top` / `left`, and repeat on scroll, resize, and content change. It works and it is well-trodden, but it is layout logic living outside the layout engine, with the coupling and jank that implies. ## The CSS model Anchor positioning gives the layout engine the relationship directly. ```css .trigger { anchor-name: --menu-trigger; } .menu { position: fixed; position-anchor: --menu-trigger; top: anchor(bottom); left: anchor(left); } ``` The pieces: - **`anchor-name`** on the anchor element assigns it a dashed-ident name. - **`position-anchor`** on the positioned element names its default anchor. - **`anchor()`** used inside inset properties resolves to an edge of that anchor's box — `anchor(bottom)`, `anchor(left)`, and so on — so `top: anchor(bottom)` means "my top edge sits at the anchor's bottom edge". - **`anchor-size()`** resolves to the anchor's width or height, useful for making a dropdown match its trigger's width. - **`position-area`** expresses a placement declaratively as a region relative to the anchor, replacing a pair of `anchor()` insets for common cases. - **`@position-try`** defines alternative placements, and **`position-try-fallbacks`** lists which ones to attempt when the preferred placement would overflow — the "flip above when there is no room below" behaviour that tooltip libraries hand-code. The positioned element must be absolutely or fixed positioned for `anchor()` to apply; it does not work on a static or relatively positioned box. ## Why it pairs with the top layer The combination that makes this genuinely new is anchoring plus the top layer. An element promoted to the top layer is positioned against the viewport, which previously meant script had to place it. With anchor positioning it can be pinned to a trigger sitting anywhere in the document while still painting above everything and escaping every clip — the two halves of the overlay problem solved without measuring anything in JavaScript. ## Shipping it while support is uneven Anchor positioning shipped first in Chromium (Chrome 125, 2024) and rollout across other engines has been gradual, so treat it as progressive enhancement rather than a baseline. Feature-detect the CSS half: ```css .menu { position: fixed; top: 4rem; left: 1rem; } @supports (anchor-name: --a) { .trigger { anchor-name: --menu-trigger; } .menu { position-anchor: --menu-trigger; top: anchor(bottom); left: anchor(left); } } ``` The rules outside the `@supports` block must produce a usable placement on their own — that is the whole discipline. Choose a fallback the design tolerates: a fixed corner placement, or a plain in-flow expanded panel rather than a floating one. Where a product genuinely requires pixel-accurate anchoring everywhere today, keep the existing JavaScript positioner and adopt anchor positioning when your support floor allows, rather than shipping two positioning systems that disagree. ## The honest tradeoff Anchor positioning removes a category of code, but it is not free judgment. Fallback chains via `@position-try` need to be designed, not guessed; anchoring to an element that itself moves or is clipped can produce an overlay pointing at nothing; and a purely declarative placement gives you fewer hooks than an imperative positioner when a designer asks for something idiosyncratic. It is the right default for the common cases — dropdown under trigger, tooltip above trigger, flip when cramped — and the interview-worthy point is that those cases stop needing scroll listeners at all.
- Why does anchor positioning matter more once overlays live in the top layer?A top-layer element is positioned against the viewport, so it has no inherent relationship to the trigger that opened it — previously only script could bridge that gap. Anchoring restores the relationship declaratively, so the overlay paints above everything, escapes every clip, and still tracks its trigger.
- What does `@position-try` add beyond a single `anchor()` placement?It defines alternative placements the browser tries when the preferred one would overflow, listed via `position-try-fallbacks`. That is the flip-above-when-cramped behaviour tooltip libraries hand-code, moved into the layout engine so it re-evaluates automatically as the anchor moves or the viewport changes.
- What is the risk of adopting it without a designed fallback?Unsupported browsers fall back to whatever your plain inset rules say, which is often a panel stuck in a corner or overlapping the trigger. The fallback must be a placement the design actually tolerates — a fixed corner, or an in-flow expanded panel — and it should be reviewed as a real state, not left to chance.
saying these in an interview costs you the question
- Assumes anchor positioning works on static elements
- Treats it as universally supported today
- Confuses anchor() with the HTML anchor element
- Thinks it replaces the top layer rather than complementing it
- Ships it with no fallback placement at all