10LOC
Chrome APIsadvanced

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-trigger to establish which anchor anchor() calls (without an explicit anchor argument) resolve against.
  • Uses anchor(--tooltip-trigger bottom) and anchor(--tooltip-trigger center) to compute top and left directly from the trigger's edges — bottom is the trigger's bottom edge, center is its horizontal center.
  • translate: -50% 0 re-centers the tooltip on that point (anchor positioning gives you a coordinate, not automatic centering).
  • position-try-fallbacks: flip-block tells 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-name values are custom idents and must start with --, same syntax as a custom property.
  • The anchored element must itself be positioned (position: fixed or absolute) — 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: none or 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-fallbacks and the flip/fallback behavior — landed later than the core anchor-name/anchor()/position-anchor trio. 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 source option — anchor positioning is what you pair with a popover to 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.