How to Position Tooltips and Popovers with CSS Anchor Positioning
Writing
WEB DEVELOPMENT
Published November 11, 20269 min read

How to Position Tooltips and Popovers with CSS Anchor Positioning

Learn how to use CSS anchor positioning to place tooltips, popovers, and menus without a JavaScript library, including viewport-edge flipping and fallbacks.

Rabinarayan Patra - Software Development Engineer

By Rabinarayan Patra

SDE II at Amazon

css-anchor-positioningcsstooltipspopover-apifrontendweb-developmentbaseline-2026

Every tooltip, dropdown, and popover you've ever built had the same annoying requirement: keep this floating box glued to that button, even when the user scrolls, resizes, or the box is about to fall off the screen. For years the answer was JavaScript. You pulled in Floating UI or Popper.js, measured bounding rectangles, listened for scroll and resize events, and recalculated positions on every frame.

CSS anchor positioning kills that whole category of code. You tether the floating element to its trigger in pure CSS, the browser handles edge collisions for you, and none of it runs on the JavaScript main thread. It works in Chrome and Edge 125+, Safari 26+, and Firefox 147+, so as of early 2026 all three major engines ship it. This guide builds a tooltip from scratch, places it with a clean grid syntax, makes it flip at the viewport edge, wires it into the native Popover API, and falls back gracefully on older browsers.

What is CSS anchor positioning, and why does it matter?

CSS anchor positioning lets you tie one element's position to another element, called the anchor, entirely in CSS. It replaces the JavaScript you used to write to keep a tooltip or dropdown glued to its trigger.

The old approach worked, but it was a lot of moving parts. A library like Floating UI calls getBoundingClientRect on the anchor, computes where the floating element should go, and re-runs that math on every scroll and resize event. That's JavaScript executing on the main thread, dozens of times a second while you scroll, just to keep a box in the right spot. It's also a dependency to install, update, and ship to every visitor.

Anchor positioning moves all of that into the browser's layout engine. You declare the relationship once in CSS, and the browser keeps the elements tethered natively, including when the page scrolls. No listeners, no recalculation in your code, no bundle cost.

How do you anchor one element to another?

You give the anchor a name with anchor-name, reference that name from the positioned element with position-anchor, then place the element's edges using the anchor() function.

Three properties, that's the whole core. Here's a tooltip pinned under a button:

/* 1. name the anchor */
.menu-button {
  anchor-name: --menu-button;
}
 
/* 2. point at it, and 3. place yourself relative to it */
.menu {
  position: absolute;
  position-anchor: --menu-button;
  top: anchor(bottom); /* my top edge sits on the anchor's bottom edge */
  left: anchor(left); /* my left edge lines up with the anchor's left edge */
  margin-top: 8px;
}

The anchor() function is the key. Inside an inset property like top or left, anchor(bottom) resolves to the position of the anchor's bottom edge. So top: anchor(bottom) reads as "put my top where the anchor's bottom is." The anchor name starts with two dashes, the same syntax as a custom property, and it's scoped so you can reuse names across independent components.

One thing trips people up early: the positioned element has to be position: absolute or fixed for anchor() and position-area to do anything at all. Without it, the browser ignores both properties and your element just sits wherever normal flow put it. If your tooltip isn't moving, that's the first thing to check.

How do you place an element with position-area?

position-area drops the element into a 3x3 grid centered on the anchor, using plain keywords like top center or bottom span-right. It's simpler than wiring up anchor() for every edge.

Picture a 3-by-3 grid laid over the anchor. The center cell is the anchor itself, and the eight surrounding cells are where you can place your element. You name a cell with a row keyword (top, center, bottom) and a column keyword (left, center, right):

.tooltip {
  position: absolute;
  position-anchor: --target;
  position-area: top center; /* sit directly above the anchor, centered */
  margin-bottom: 8px;
}

That single position-area line replaces two or three anchor() calls. The span-* keywords let an element stretch across cells, so position-area: bottom span-right places it below the anchor and extends it rightward, which is exactly what you want for a dropdown menu that aligns to the left edge of its button and grows down and to the right.

There's a matching function for size, too. anchor-size() reads the anchor's dimensions, so width: anchor-size(width) makes a dropdown exactly as wide as the button that opened it. That's another measurement you used to do in JavaScript and can now hand to the browser.

How do you make a popover flip at the viewport edge?

You list fallback positions with position-try-fallbacks. The browser uses your preferred position until the element would overflow the viewport, then automatically switches to a fallback.

This is the feature that used to justify the whole JavaScript library. A tooltip above a button is fine, until that button is near the top of the screen and the tooltip gets clipped. With anchor positioning, you describe the fallback and the browser does the flipping:

.tooltip {
  position: absolute;
  position-anchor: --target;
  position-area: top center;
  position-try-fallbacks: flip-block; /* overflow the top? flip to below */
}

flip-block mirrors the position across the block axis, so top center becomes bottom center when there isn't room above. For finer control, define a named try option with the @position-try at-rule and list several fallbacks in order:

@position-try --below {
  position-area: bottom center;
  margin-top: 8px;
}
 
.tooltip {
  position-area: top center;
  margin-bottom: 8px;
  position-try-fallbacks: --below, flip-inline; /* tried in order until one fits */
}

The browser walks your fallback list top to bottom and uses the first option where the element fits on screen. You get smart, collision-aware positioning without measuring a single rectangle yourself.

How do you wire this into the HTML Popover API?

Anchor positioning pairs directly with the native popover attribute. You set anchor-name on the trigger and position-anchor on the popover, and the browser tethers the popover to the button even though the popover lives in the top layer.

<button popovertarget="menu" style="anchor-name: --menu-btn">Options</button>
 
<div id="menu" popover style="position-anchor: --menu-btn; position-area: bottom span-right">
  <a href="/profile">Profile</a>
  <a href="/settings">Settings</a>
  <a href="/logout">Log out</a>
</div>

The Popover API gives you the open/close behavior, light-dismiss, and top-layer rendering for free. Anchor positioning gives you the placement. Together they're a complete dropdown menu with no positioning JavaScript at all. If you want to style the open and closed states too, the CSS :open pseudo-class handles that side of the same component, so the entire menu (state and placement) lives in CSS.

Because the popover renders in the top layer, it escapes overflow: hidden and stacking-context traps that used to break dropdowns inside scrolling containers. The anchor reference still resolves correctly across that boundary.

How do you add a fallback for older browsers?

You wrap the anchor rules in @supports (anchor-name: --x) and ship a classic absolute-positioning fallback outside it. Older browsers get a usable position, modern ones get true anchoring.

Progressive enhancement is the safe way to adopt this today. Write the boring fallback first, then layer the anchor positioning on top only where it's supported:

/* fallback: plain absolute positioning, works everywhere */
.tooltip {
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translateX(-50%);
}
 
@supports (anchor-name: --x) {
  .anchor {
    anchor-name: --target;
  }
  .tooltip {
    position-anchor: --target;
    position-area: top center;
    bottom: auto;
    left: auto;
    transform: none;
  }
}

The feature query checks whether the browser understands anchor-name. If it does, the anchored rules win and override the fallback. If it doesn't, the browser skips the whole block and the element stays where the basic CSS put it. One more useful tool here is position-visibility: anchors-visible, which automatically hides the anchored element when its anchor scrolls out of view, so a tooltip doesn't float over unrelated content after the trigger leaves the screen.

Should you delete Floating UI today?

For tooltips, dropdowns, and popovers in modern browsers, you genuinely can. Pair anchor positioning with an @supports fallback and you lose nothing on older browsers while shipping less code everywhere else.

The win that's easy to undersell is where the work happens. JavaScript positioning libraries run on the main thread, competing with your app's actual logic and recalculating on every scroll tick. Anchor positioning runs in the browser's layout engine, so scrolling stays smooth and your bundle gets smaller. You'd keep a library only to support browsers without a fallback path, or for positioning logic that CSS can't yet express. Stack anchor positioning with the Popover API and the :open pseudo-class, and the interactive components we've been shipping JavaScript for since 2015 are now mostly just CSS. That's the part worth getting excited about.

For the full reference, see the MDN CSS anchor positioning guide and Chrome's introduction to the anchor positioning API, which covers the design and edge cases in depth.

Keep Reading

Frequently Asked Questions

What is CSS anchor positioning?

CSS anchor positioning is a set of CSS features that tether one element's position to another, called the anchor, so you can place tooltips, popovers, and menus relative to a trigger without JavaScript. You name an anchor with anchor-name, point at it with position-anchor, then place the element with the anchor() function or the position-area property.

Which browsers support CSS anchor positioning?

It works in Chrome and Edge 125+ (2024), Safari 26+ (2025), and Firefox 147+ (January 2026). With all three major engines shipping it, anchor positioning became broadly available in early 2026, though you should still ship a fallback for older versions using @supports.

Can CSS anchor positioning replace Floating UI or Popper.js?

For most tooltips, dropdowns, and popovers in modern browsers, yes. It handles anchoring and viewport-edge flipping natively. You'd keep a JavaScript library only if you must support older browsers without a fallback, or you need positioning logic that CSS can't express.

How do you make an anchored element flip at the screen edge?

Use position-try-fallbacks with a keyword like flip-block, or define a custom @position-try option. The browser tries your preferred position first and automatically switches to a fallback when the element would overflow the viewport.

Rabinarayan Patra - Software Development Engineer

Rabinarayan Patra

SDE II at Amazon. Previously at ThoughtClan Technologies building systems that processed 700M+ daily transactions. I write about Java, Spring Boot, microservices, and the things I figure out along the way. More about me →

X (Twitter)LinkedIn

Stay in the loop

Get the latest articles on system design, frontend and backend development, and emerging tech trends, straight to your inbox. No spam.