Here is the code you have written, or something close to it, for every tooltip on every project:
import { computePosition, flip, shift, offset } from '@floating-ui/dom';
const { x, y } = await computePosition(button, tooltip, {
placement: 'top',
middleware: [offset(8), flip(), shift({ padding: 8 })],
});
Object.assign(tooltip.style, {
left: `${x}px`,
top: `${y}px`,
});
Then you wire it to a ResizeObserver so it recomputes when the button moves. Then a scroll listener for the same reason. Then you discover the tooltip clips inside an overflow: hidden parent and spend an afternoon understanding stacking contexts. The library is excellent — it handles real complexity. But the core problem it's solving — "position this element relative to that element, and keep it there" — is something the browser should have always owned.
As of 2026, it does — in every major engine.
The anchor model
CSS Anchor Positioning works in two steps: an element declares itself an anchor, then a floating element positions itself relative to it.
The anchor declares its name using a custom property (dashed-ident, same syntax as CSS variables):
.trigger-button {
anchor-name: --my-tooltip-anchor;
}
The floating element links to it and uses the anchor() function to express where it should sit:
.tooltip {
position: absolute;
position-anchor: --my-tooltip-anchor;
bottom: anchor(top); /* align my bottom edge to anchor's top edge */
left: anchor(center); /* align my left edge to anchor's horizontal center */
transform: translateX(-50%);
margin-bottom: 8px;
}
The anchor() function is the engine. Its argument names which edge or midpoint of the anchor to reference: top, bottom, left, right, center, start, end. Those values are then used in standard inset properties — top, bottom, left, right, inset-inline-start, etc.
No JavaScript. No event listeners. The browser computes the anchor's position in the layout, resolves the floating element's insets against it, and keeps both in sync through reflows, resizes, and scrolls.
position-area for common placements
For the eight standard compass placements, there is a higher-level shorthand: position-area. It describes where the floating element lands relative to a 3×3 grid centered on the anchor:
.tooltip {
position: absolute;
position-anchor: --my-tooltip-anchor;
position-area: top center; /* above, horizontally centered */
margin-bottom: 8px;
}
The grid vocabulary: top, center, bottom on the block axis; left, center, right (or the writing-mode-aware start, center, end) on the inline axis. Combine them to get any of the eight placements:
position-area: bottom right; /* below and to the right */
position-area: top span-all; /* above, stretching the full width of the anchor */
position-area: inline-start; /* to the inline-start side (left in LTR) */
Use position-area for standard placements. Use anchor() directly when you need precise edge-to-edge control — for example, a dropdown that should align its left edge exactly with the anchor's left edge rather than centering.
Viewport overflow: position-try-fallbacks
The main reason you installed flip() and shift() middleware is viewport overflow: the tooltip was about to render off-screen, so the library flips it to the other side. CSS Anchor Positioning handles this natively with position-try-fallbacks:
.tooltip {
position: absolute;
position-anchor: --my-tooltip-anchor;
position-area: top center;
margin-bottom: 8px;
position-try-fallbacks: bottom center, left center, right center;
}
The browser tries top center first. If the tooltip overflows the viewport in that position, it tries bottom center. If that overflows, it tries left center, then right center. The first placement that fits without overflow wins.
For the common case of just flipping across one axis, there are keyword shorthands:
position-try-fallbacks: flip-block, flip-inline;
flip-block means "try the opposite block edge" — if you specified top, try bottom. flip-inline means "try the opposite inline side." You can combine them: flip-block flip-inline tries the diagonally opposite placement.
This replaces the flip() and shift() middleware for the common use cases without any JavaScript.
🎮 Try it yourself
▶️ Open the interactive playground →
Runs right in your browser — poke at it and watch the concept react live.
What you still need a library for
CSS Anchor Positioning handles positioning and overflow. It does not handle every case.
The position-try-fallbacks detection is binary: either the element fits at a given placement or it doesn't. It does not compute how much space is available at each candidate and pick the largest. If you need "prefer below, but only if there are at least 200px of vertical clearance," you still need JavaScript.
Containment is another gap. If your floating element is inside an overflow: hidden parent — a scroll container, a card with clipping — it will clip there just like any other positioned element. The fix is to render the floating element in the top layer, which the Popover API handles. Combining popover="manual" with anchor positioning is the full modern stack: the popover attribute gets you out of the stacking context, anchor positioning places you correctly relative to the trigger.
Arrow indicators — the triangle that connects the tooltip to the trigger — are not automatic. You can compute the arrow position manually using anchor() values in a ::before pseudo-element, but it takes a few extra lines and isn't as polished as a library's arrow plugin out of the box.
Browser support
CSS Anchor Positioning is Baseline 2026: Chrome/Edge 125 (2024), Safari 26 (2025), and Firefox 147 (early 2026) — Firefox was the last engine to ship it, which is what tipped the feature into Baseline. It's now supported unflagged in every current browser. No flag, no polyfill, no build step.
🧠 Test yourself
Think it clicked? Take the 8-question quiz →
Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.
The takeaway
Floating UI is an excellent library — but most of what it does at the positioning layer is the browser's job. For the common cases — tooltips, dropdowns, label overlays, context menus — anchor-name and position-anchor replace the installation entirely. position-area replaces the placement config. position-try-fallbacks replaces flip() and shift().
The pattern is the same one you have seen with the Popover API and scroll-driven animations: the browser absorbed the use case that a library existed to fill. You do not have to stop using Floating UI immediately. But the next tooltip you build, try starting with two CSS properties. The library is an escape hatch for the complex cases, not the baseline anymore.
Thanks for reading! Let's stay connected:
- ⭐ GitHub — follow me and star the projects: github.com/parsajiravand
- 📸 Instagram — frontend best practices, daily: @bestpractice___
- 💼 LinkedIn — linkedin.com/in/parsa-jiravand
- ✉️ Email (work & contract inquiries): bestpractice2026@gmail.com
Top comments (1)
This is fantastic! The "Baseline 2026" mention makes me wonder about current browser support