Anchor Positioning Is Going to Kill the Floating UI Libraries
For years, every dropdown, tooltip, popover, autocomplete and combo-box on the web has used the same trick - measure the trigger element with getBoundingClientRect(), measure the floating element, do some maths, write top / left styles in a position: absolute element. Then listen for scroll, resize, and intersection events. Then update the maths. Then redraw. Then hope nothing flickered.
Libraries grew up around this dance. Popper.js, then floating-ui, Headless UI, Radix; all of them ship hundreds of lines of carefully tuned positioning logic. Pick a placement (bottom-start, top-end), opt into collision detection, watch for overflow, flip when there’s no room, shift when the screen is small.
It works. But it’s a workaround.
Before any of that - there’s title
The browser has always had a zero-effort tooltip: the title global attribute. Hover over the paragraph below and you’ll see it.
We did have title - hover here to see.
Add multiline support too - hover here to see.
One attribute, no JavaScript, no library. For a footnote or a quick “what does this abbreviation mean”, it was fine.
But the moment you want anything more than the default, title falls apart:
- No mobile support. There’s no hover on a touch screen, so the tooltip is completely invisible on phones and tablets - which is most of your traffic.
- Zero styling control. The browser renders it however it wants. You can’t change the font, colours, padding, max-width, or delay. No two browsers render it identically.
- Placement is out of your hands. It always appears near the cursor, typically just below. If it’s near the bottom of the viewport it clips; you can’t tell it to flip upward.
- Accessibility is poor. Screen readers handle
titleinconsistently - some read it, some skip it, some only read it if there’s no other accessible name. It’s not a reliable way to convey meaning.
So title is fine for decoration. It’s not fine for UI. Which is why the workaround stack - getBoundingClientRect, event listeners, floating-ui, exists in the first place.
CSS anchor positioning replaces the whole thing with a few CSS properties.
What it actually does
Browsers can now natively position one element relative to another, declaratively, without a single line of JavaScript. Here’s the simplest example - a tooltip anchored to a button:
<button id="trigger">Click me</button>
<div id="tooltip">I'm anchored</div>
#trigger {
anchor-name: --my-trigger;
}
#tooltip {
position: absolute;
position-anchor: --my-trigger;
top: anchor(bottom);
left: anchor(start);
margin-top: 4px;
}
That’s the entire positioning logic. The browser tracks the anchor’s position (across scrolls, resizes, transforms, container changes) and keeps the tooltip glued to it. No measurement code, no listeners, no useLayoutEffect.
Three things to notice -
anchor-nameis a custom identifier (always---prefixed) you put on the element you want to anchor to.position-anchoron the floating element points at that name.anchor()returns the anchor’s edge -bottom,top,start,end,center, etc.
Collision handling without writing a single line of JS
The hard part of positioning libraries isn’t placement, it’s collision. What if the tooltip would overflow the viewport? Floating-ui has a whole middleware concept for this - flip, shift, hide, autoPlacement. Each is a function that runs every frame.
CSS gets you most of it with position-try-fallbacks:
#tooltip {
position: absolute;
position-anchor: --my-trigger;
/* Default position: below the trigger */
top: anchor(bottom);
left: anchor(start);
/* If that overflows, try these in order */
position-try-fallbacks: --above-trigger, --right-of-trigger;
}
@position-try --above-trigger {
top: auto;
bottom: anchor(top);
left: anchor(start);
}
@position-try --right-of-trigger {
top: anchor(top);
left: anchor(end);
bottom: auto;
}
The browser tries each fallback until one fits. No JavaScript. No requestAnimationFrame loop. No “the tooltip flickers when scrolling” bug to chase.
Real-world example: a select-style menu
Here’s a more complete pattern - a dropdown menu that opens below the button by default, but flips above if there’s no room.
<button id="menu-btn" popovertarget="menu">Options</button>
<menu id="menu" popover>
<li><button>Edit</button></li>
<li><button>Duplicate</button></li>
<li><button>Delete</button></li>
</menu>
#menu-btn {
anchor-name: --menu-btn;
}
#menu {
position: absolute;
position-anchor: --menu-btn;
top: anchor(bottom);
left: anchor(start);
margin-top: 4px;
min-width: anchor-size(width);
position-try-fallbacks: flip-block;
}
anchor-size(width) makes the menu at least as wide as its trigger - perfect for select-style dropdowns. flip-block is a built-in shorthand fallback that says “if I overflow vertically, flip me to the other side”.
Combine that with the popover attribute (also native, also new) and you have a full dropdown menu with no positioning library, no event listeners, and no JS at all.
Things floating-ui still does better (for now)
I want to be fair - anchor positioning isn’t a one-for-one replacement yet:
- Animations on enter/leave. floating-ui ties nicely into transition libraries. With CSS popover and view transitions, you can get there, but the API is still settling.
- Arrow rendering. Drawing the little triangle that points at the anchor still needs some thought (often a separate anchor + CSS clip-path).
- Virtual elements. Anchoring to a position that isn’t a real DOM element (like a mouse cursor for a context menu) needs a 1×1 invisible div as a stand-in.
- Older browsers. Safari only added support relatively recently. There’s a polyfill - oddbird/css-anchor-positioning - that gets you most of the way.
The MDN page at MDN/Web/CSS/CSS_anchor_positioning has the full reference, and the Chrome team has a great write-up at developer.chrome.com that goes deeper than I’ve gone here.
What this means for the libraries
I don’t think floating-ui is going away tomorrow - production codebases pin specific versions, and there’s a long tail of edge cases that JS handles better. But I do think the reason people reach for it is changing.
Three years ago, you needed floating-ui because there was no other option. Today, for a fresh project, the question is “do I really need this dependency?”. For 80% of cases - tooltips, popovers, simple menus - the answer is starting to be “no”.
The bigger win, honestly, is the elimination of glue code. Every floating-ui integration I’ve seen has the same shape: a useFloating hook, refs threaded down to two elements, an update function tied into a window listener, and a portal because the menu can’t be inside a clipped container. With anchor positioning, all of that disappears. The CSS is the integration.
That’s the part that’s hard to walk back from once you’ve felt it. A dropdown that just works, declared in five lines of CSS, with no React state, no refs, no effects - it’s the kind of thing that makes you wonder how we ended up writing all that JavaScript in the first place.