← dominic.balass.one

Anchor Positioning Playground

Tether any element to any other element in pure CSS. Drag the purple button around: the tooltip stays glued to it with zero positioning JavaScript. No scroll listeners, no resize listeners, no flip math.

Try it

Drag with a pointer, or move with the arrow keys when focused.
Placement
Support: checking… Anchor: — Tooltip: — Active side: —

Drag the button or the tooltip near an edge with auto-flip on and watch the tooltip flip sides by itself. Scroll this page or resize the window: it stays glued. The button also moves with arrow keys when focused. The script below only moves the anchor; the tooltip's position is computed entirely by CSS.

Why not just JavaScript?

The old way works, but you own every edge case: measuring, scroll, resize, zoom, and the flip math when the tooltip would overflow. Anchor positioning hands all of that to the browser.

The old way JS you maintain

function placeTip() {
  const a = anchor.getBoundingClientRect();
  const t = tip.getBoundingClientRect();
  let top = a.bottom + 10;
  let left = a.left + a.width / 2 - t.width / 2;
  // flip when overflowing, by hand
  if (top + t.height > innerHeight)
    top = a.top - t.height - 10;
  if (left < 0) left = 8;
  if (left + t.width > innerWidth)
    left = innerWidth - t.width - 8;
  tip.style.transform =
    `translate(${left}px, ${top}px)`;
}
addEventListener('scroll', placeTip,
  { passive: true });
addEventListener('resize', placeTip);
new ResizeObserver(placeTip)
  .observe(document.body);
placeTip();

Anchor positioning the browser owns it

#anchor { anchor-name: --tip; }

#tip {
  position: absolute;
  position-anchor: --tip;
  /* margin would not flip; bake the gap into the inset */
  top: calc(anchor(bottom) + 10px);
  left: anchor(center);
  translate: -50% 0;
  /* flip automatically near edges */
  position-try-fallbacks:
    flip-block, flip-inline;
}
The flip is the part people underestimate. Hand-rolled flip logic breaks on zoom, on nested scrollers, and on rapid resizes. position-try-fallbacks: flip-block, flip-inline re-runs inside the browser's layout pass, so it can never go stale.

Honest notes

One gotcha: the anchor and the positioned element must be in the same shadow tree, and the positioned element needs position: absolute (or fixed). Also note this demo uses explicit anchor() insets rather than the position-area shorthand: same result, wider support (the shorthand was renamed from inset-area mid-rollout).