CSS anchor positioning for a tooltip anchored to its trigger
Published September 18, 2026
.trigger {
anchor-name: --tooltip-trigger;
}
.tooltip {
position: fixed;
position-anchor: --tooltip-trigger;
top: calc(anchor(--tooltip-trigger bottom) + 8px);
left: anchor(--tooltip-trigger center);
translate: -50% 0;
position-try-fallbacks: flip-block;
}What
CSS anchor positioning lets a positioned element (the tooltip) read the geometry of a different element (the trigger) directly in CSS — anchor-name tags the trigger, anchor() and position-anchor let the tooltip position itself relative to it, with position-try-fallbacks for when there's no room.
Why it matters
Positioning a tooltip relative to an arbitrary trigger has always meant JavaScript: getBoundingClientRect() on the trigger, math to place the tooltip, a scroll/resize listener to keep it in sync, and — for the "flip to the other side if it'd overflow the viewport" behavior every tooltip library has — a collision-detection pass. That's the entire reason Floating UI (formerly Popper) exists as a dependency.
Anchor positioning moves all of that into the CSS engine, which already knows the trigger's geometry on every frame for free. No JS runs on scroll or resize; the browser just repositions the tooltip as part of layout, the same way it repositions anything else.
How it works
anchor-name: --tooltip-trigger on .trigger gives it a name any other element can reference. .tooltip then:
- Sets
position-anchor: --tooltip-triggerto establish which anchoranchor()calls (without an explicit anchor argument) resolve against. - Uses
anchor(--tooltip-trigger bottom)andanchor(--tooltip-trigger center)to computetopandleftdirectly from the trigger's edges —bottomis the trigger's bottom edge,centeris its horizontal center. translate: -50% 0re-centers the tooltip on that point (anchor positioning gives you a coordinate, not automatic centering).position-try-fallbacks: flip-blocktells the browser: if this placement overflows the containing block, try flipping to the opposite block-axis side (above the trigger instead of below) before giving up.
Gotchas
anchor-namevalues are custom idents and must start with--, same syntax as a custom property.- The anchored element must itself be positioned (
position: fixedorabsolute) —anchor()is invalid on an in-flow element. - An anchor reference only resolves if the anchor element is in scope for the query element — roughly, an anchor hidden by
display: noneor removed from the DOM breaks the link silently, and the tooltip falls back to its static position. - Safari's support for the full feature set — specifically
position-try-fallbacksand the flip/fallback behavior — landed later than the coreanchor-name/anchor()/position-anchortrio. Test the fallback path specifically if Safari is in your support matrix, rather than assuming parity with Chrome/Firefox.
Browser support: core anchor positioning (anchor-name, position-anchor, anchor()) is supported in current Chrome, Firefox, and Safari. It's recent enough across all three engines that a quick compatibility check against your actual minimum-supported versions is worth doing before shipping this as the only positioning mechanism, with no JS fallback, for anything load-bearing.
Related
- The Popover API's
sourceoption — anchor positioning is what you pair with apopoverto place it relative to its invoker. - Floating UI / Popper — the JS-based approach this replaces for browsers that support anchor positioning; still the right choice if you need to support browsers without it.