CSS Anchor Positioning Table
| Piece | What it does | Field note |
|---|---|---|
anchor-name: --tip | Name an anchor | CSS custom-property-style naming - the dash prefix |
position-anchor: --tip | Bind a positioned el | The follower declares WHICH anchor it tracks |
position-area: top span-right | Position by grid | Nine-zone grid around the anchor - no math coordinates |
anchor(--tip top) | In inset properties | top: anchor(bottom) = flush under the anchor - the primitive |
@position-try | Fallback positions | Flip to bottom when top overflows - declared, not scripted |
position-visibility: anchors-visible | Scroll-aware hiding | Hide the follower when the anchor scrolls away |
anchor-size(--tip width) | Match dimensions | Width follows the anchor's width - tooltips that fit |
Chrome-first | Check @supports | Firefox/Safari behind flags - @supports gate + popover fallback |
Anchor positioning kills the tooltip-positioning library: name the anchor (anchor-name: --tip), declare the follower (position-anchor: --tip), and place it with position-area's nine-zone grid or the anchor() function inside inset properties - positioning RELATIVE TO ANOTHER ELEMENT as pure CSS.
Bottom line: @position-try declares fallbacks declaratively - 'try top; if it overflows, flip to bottom' is the exact logic floating-ui shipped in kilobytes of JS, now browser-recomputed on every layout change. position-visibility: anchors-visible handles the scroll-away case; anchor-size matches dimensions.
The honest part: anchor positioning is Chrome-first (2024-2025) - Firefox and Safari are still catching up. Gate with @supports (anchor-name: --x), keep the popover plus manual positioning as the fallback, and treat this as progressive enhancement for the tooltip stack.
How to use
- Wire a tooltip: .btn { anchor-name: --tip; } .tooltip { position: fixed; position-anchor: --tip; position-area: bottom; } - bound and placed in six lines, no JS.
- Declare the flip: @position-try --flip { position-area: top; } then position-try-fallbacks: --flip - overflow triggers the swap, recomputed on resize and scroll.
- Match the width: width: anchor-size(--tip width) - the tooltip follows the button's width, the dropdown follows its trigger, with zero measuring code.
Frequently asked questions
What problem does anchor positioning solve that absolute positioning could not?
Relative-to-another-element positioning. position: absolute anchors to the nearest positioned ANCESTOR - always a parent-child relationship, so tooltips (siblings in practice) needed JavaScript: measure the button, compute viewport coordinates, write top/left, and recompute on every scroll, resize and layout shift. Anchor positioning makes ANY element an anchor by name, and the follower's position resolves against it declaratively - the browser recomputes during layout itself, so scroll, resize, font-load and container changes all just work, with no listeners and no main-thread math. The library's core loop became a CSS declaration.
How do position-area and the anchor() function divide the work?
position-area is the ergonomic 80%: it lays the follower into a NINE-ZONE GRID around the anchor (top, bottom, span-right, center...) - one descriptive value instead of coordinates, and spanning values (span-right) let the tooltip use leftover space. The anchor() function is the precise primitive: inside inset properties (top: anchor(bottom)), it resolves to the anchor's edge position - composable with calc for offsets (top: calc(anchor(bottom) + 8px)). Rule of thumb: reach for position-area first (it also auto-handles the containing block), drop to anchor() when the placement needs custom math the grid cannot express.
What does @position-try change about overflow handling?
It makes fallbacks a DECLARED LIST. The classic positioning-library problem: a tooltip placed above the button overflows the viewport top - flip below, which may overflow the bottom, so try right, then adjust alignment... that try-chain was hundreds of lines of measurement code. @position-try defines named fallback positions (each a block of position-area/inset/margin tweaks), and position-try-fallbacks lists them in priority order - the browser tries each until one fits the viewport, recomputing on every layout change. The library's smartest loop became declarative CSS, including the scroll-connected variant: position-visibility: anchors-visible hides the follower when its anchor scrolls out of view.
How should this ship today, given the browser split?
As gated enhancement over a working fallback. The support story: Chrome has it (2024+), Firefox and Safari are in development - so @supports (anchor-name: --x) wraps the anchor declarations, and the base style keeps a usable fallback (a popover positioned manually, or simply static placement). The pairing with the Popover API is natural: popovers solve the top-layer/show-hide half (no z-index wars, light-dismiss free), anchor positioning solves the placement half - together they replace the tooltip widget. Teams already shipping floating-ui can migrate incrementally: the JS keeps working everywhere, and @supports browsers shed the JavaScript on the hot path.