Dev.to WebDev 🛠 Dev 👁 0 📖 5 min read

CSS anchor positioning: tooltips that flip

This note has an interactive demo. Try it in the original A tooltip that sticks to its button and jumps to the other side when it hits an edge used to mean a positioning library. Now it takes a handful of CSS lines, and

This note has an interactive demo. Try it in the original

A tooltip that sticks to its button and jumps to the other side when it hits an edge used to mean a positioning library. Now it takes a handful of CSS lines, and the core of it has been Baseline since January 2026.

Drag the orb in the demo above toward the top edge. The card that was sitting above it jumps below. Push it to a side with left selected and it moves to the right. The two dashed guides and the small --orb label follow along too. The only thing the JavaScript does is move the orb. Where everything else goes is decided by CSS.

What it is

CSS anchor positioning lets you place an absolutely or fixed positioned element relative to another element, the anchor, instead of relative to its containing block. That's the problem behind every tooltip, dropdown, popover menu and "new" badge. Until now you had to measure with getBoundingClientRect(), listen for scroll and resize, and handle the edge cases yourself, or ship a library that did it for you.

It works in two steps. You give the anchor a name, then you attach the positioned element to that name:

/* the anchor */
.orb {
  anchor-name: --orb;
}

/* the anchored element */
.card {
  position: absolute;
  position-anchor: --orb;
  position-area: top;
  margin: 14px;
  position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
}

That's the exact rule the demo's card uses (the visual styling is left out).

How the pieces fit

anchor-name declares an element as an anchor. The name is a dashed ident, like a custom property: --orb, --menu-button.

position-anchor goes on the positioned element and says which anchor it belongs to. The element has to be position: absolute or position: fixed.

position-area is the easy way to place it. Picture a 3×3 grid with the anchor in the middle cell. top puts the element above the anchor, right to its right, and top right in the corner cell. The segmented control in the demo switches between these values live.

anchor() is the precise way. You use it inside the inset properties (top, left, right, bottom), and it returns the position of one of the anchor's sides. The vertical guide in the demo is just this:

.guide.v {
  position: absolute;
  position-anchor: --orb;
  top: 0;
  bottom: 0;
  left: anchor(center); /* always passes through the orb's center */
  width: 1px;
}

position-try-fallbacks is where the flipping comes from. If the element would overflow its containing block, the browser tries the options you list, in order, until one fits. flip-block mirrors it across the block axis (above becomes below). flip-inline mirrors it across the inline axis (left becomes right). And flip-block flip-inline, written as a single option, does both at once. That combined option matters: MDN's guide shows that with only flip-block, flip-inline, a tooltip pushed into a corner has no option that fits, and the browser falls back to the original position, which overflows. Try it in the demo: pick top right, push the orb into the top-right corner, and the card ends up bottom-left.

Then switch Fallbacks off and push it against an edge again. With position-try-fallbacks: none the card stays where you asked for it, even if that means sticking out of the board or covering the anchor. The "Rendered" pill in the demo tells you which side the card actually landed on. It gets that by reading the final position, not by calculating it.

Browser support and gotchas

According to the web-features data, anchor positioning became Baseline newly available on 13 January 2026, with Firefox 147. Chrome and Edge have had it since 125, and Safari since 26. Some pieces arrived later in Chromium: position-try-fallbacks in 128, position-area in 129. The feature as a whole isn't marked Baseline yet, because a few newer additions, such as the anchor-visible value of position-visibility, haven't landed everywhere.

Things that tripped me up, or that I'd want to know before shipping this:

  • Always set position-anchor explicitly. Browsers have disagreed about its initial value. According to MDN's compat data, Chrome went through implicit, auto and none before settling on normal in 151, and Firefox 147–150 and Safari 26 use auto. If you name the anchor yourself, none of that matters.
  • DOM order matters. The spec only accepts an anchor that is laid out before the positioned element. If both are absolutely positioned in the same containing block, the anchor has to come earlier in the DOM. In the demo, the orb comes before the card in the HTML.
  • A hidden anchor doesn't count. If the anchor is display: none, MDN says the positioned element falls back to its nearest positioned ancestor.
  • Duplicate names pick the last one. If several elements share an anchor-name, the positioned element attaches to the last one in source order. In repeated components (a list of cards, each with its own tooltip) use anchor-scope to limit each name to its own subtree.
  • Popovers get an anchor for free. A popover opened with popovertarget or commandfor gets an implicit anchor reference to its invoking button. If you then place it with anchor(), MDN explains you also need to reset the popover's default positioning and use position-anchor: auto.

For older browsers, the demo detects support with CSS.supports('anchor-name: --a') (plus position-area and position-try-fallbacks). If any of those is missing, it shows a notice and positions the card with a small getBoundingClientRect() script that mimics the same flips. In CSS you can do the same check with @supports (anchor-name: --a) and keep a simple static position as the default.

What to keep an eye on

  • position-visibility: hiding the anchored element automatically when its anchor scrolls out of view (anchor-visible) is still not supported everywhere.
  • Custom fallbacks with @position-try: when flipping isn't enough, you can define named positions with their own size and margins. They're part of the same Baseline core, and worth trying for menus that need to shrink.
  • Removing libraries: if a project only uses a positioning library for tooltips and menus, this is a good time to check how much of that code you still need.

Sources

Originally published at richi.codes.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.