CSS Anchor Positioning Table

PieceWhat it doesField note
anchor-name: --tipName an anchorCSS custom-property-style naming - the dash prefix
position-anchor: --tipBind a positioned elThe follower declares WHICH anchor it tracks
position-area: top span-rightPosition by gridNine-zone grid around the anchor - no math coordinates
anchor(--tip top)In inset propertiestop: anchor(bottom) = flush under the anchor - the primitive
@position-tryFallback positionsFlip to bottom when top overflows - declared, not scripted
position-visibility: anchors-visibleScroll-aware hidingHide the follower when the anchor scrolls away
anchor-size(--tip width)Match dimensionsWidth follows the anchor's width - tooltips that fit
Chrome-firstCheck @supportsFirefox/Safari behind flags - @supports gate + popover fallback
Reference: the MDN anchor positioning reference. Anchor positioning kills the tooltip-positioning library: name the anchor (anchor-name: --tip), declare the follower (position-anchor), 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, recomputed by the browser on every layout change. Bottom line: @position-try declares fallbacks (flip below when the top overflows - the floating-ui core use case, now CSS), position-visibility handles scroll-away, and it is Chrome-first: gate with @supports and keep the popover/manual-position fallback until the others ship. Related tools: details table (the JS-free disclosure sibling), logical properties table (inset properties the anchor() feeds), and observer table (the JS positioning this replaces).

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

  1. Wire a tooltip: .btn { anchor-name: --tip; } .tooltip { position: fixed; position-anchor: --tip; position-area: bottom; } - bound and placed in six lines, no JS.
  2. Declare the flip: @position-try --flip { position-area: top; } then position-try-fallbacks: --flip - overflow triggers the swap, recomputed on resize and scroll.
  3. 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.

Related tools