CSS Anchor Positioning — tooltip libraries are obsolete.
Floating-UI is 30 KB minified. Popper.js is 8 KB. Tippy is 25 KB. Combined, the JavaScript industry has shipped petabytes of code just to position one element relative to another. CSS Anchor Positioning makes it five lines of CSS, zero JavaScript. It's in Chrome 125, Safari 18.4, and Firefox is implementing. Here's the complete guide.
01The 25 KB problem
Every modern app has tooltips, popovers, dropdown menus, autocomplete suggestions, and date pickers. Each of these needs to position one element (the "floater") relative to another (the "anchor"). The math is annoying: the floater might overflow the viewport on the right, so it should flip to the left. If it overflows downward, flip upward. If it's near a corner, you might need both.
For 15 years, the answer was: ship a JavaScript library. The library measures the anchor's bounding rect, measures the floater's size, computes the optimal placement, listens to scroll and resize events, recomputes on every change. It's tens of kilobytes of code, runs on every interaction, and ships on virtually every web app on Earth.
It was always a layout problem. The browser already knows where everything is. CSS Anchor Positioning is the long-overdue admission that this should live in the rendering engine, not in user code.
02The two-property core
The whole feature reduces to two properties:
/* 1. Name an element as an anchor */
.button {
anchor-name: --my-btn;
}
/* 2. Position another element relative to it */
.tooltip {
position: absolute;
position-anchor: --my-btn;
top: anchor(bottom); /* aligns to the anchor's bottom edge */
left: anchor(left); /* aligns to the anchor's left edge */
}
The anchor() function takes the side of the anchor element you want to align to. The tooltip's top says "match the anchor's bottom" — so the tooltip starts where the anchor ends. The tooltip's left says "match the anchor's left" — so they line up on the left edge. No JavaScript. The browser updates the position on scroll, resize, and any layout change.
03The shortcut: position-area
Writing four anchor() calls for top/right/bottom/left every time gets tedious. CSS gives you a shorthand: position-area, which uses spatial words to describe placement.
.tooltip {
position: absolute;
position-anchor: --my-btn;
position-area: bottom; /* directly below, centered */
}
.popover {
position-anchor: --my-btn;
position-area: top right; /* upper-right corner */
}
.menu {
position-anchor: --my-btn;
position-area: block-end inline-start; /* RTL-aware! */
}
The values include all the obvious spatial words (top, bottom, left, right, center) plus the logical equivalents (block-start, block-end, inline-start, inline-end) that flip automatically in RTL contexts. If you're building anything multilingual, use the logical ones.
04The killer feature — position-try
What makes this actually replace Floating-UI is position-try-fallbacks. When the floater would overflow the viewport, you can specify alternative placements the browser should try in order.
.tooltip {
position: absolute;
position-anchor: --my-btn;
position-area: bottom;
/* If 'bottom' overflows, try these in order */
position-try-fallbacks: top, right, left;
}
The browser will try placing the tooltip below. If it overflows the viewport, try above. If that overflows, try right. Then left. The browser picks the first option that fits — the same logic Floating-UI ships as 30 KB of JavaScript, now in three lines of CSS.
You can also define custom fallback positions using @position-try:
@position-try --shifted-up {
top: anchor(top);
bottom: auto;
translate: 0 -8px; /* extra offset for spacing */
}
.tooltip {
position-try-fallbacks: --shifted-up, flip-block;
}
The keyword flip-block means "swap top and bottom anchoring." There's also flip-inline for left/right swaps. These cover 95% of fallback cases.
05The classic tooltip with an arrow
The complete tooltip — arrow that points at the anchor, repositions when flipped — is now achievable in pure CSS:
.button { anchor-name: --btn; }
.tooltip {
position: absolute;
position-anchor: --btn;
position-area: bottom;
position-try-fallbacks: top;
margin-top: 8px;
padding: 8px 12px;
background: var(--ink);
color: var(--bg);
border-radius: 6px;
font-size: 13px;
white-space: nowrap;
}
/* Arrow — a rotated square at the top edge */
.tooltip::before {
content: '';
position: absolute;
bottom: 100%;
left: 50%;
translate: -50% 50%;
width: 10px;
height: 10px;
background: var(--ink);
rotate: 45deg;
}
06Dropdown menus — finally
Dropdown menus are anchor positioning's other big win. Combined with the new popover attribute (also universal), you get a fully accessible dropdown menu in HTML/CSS only — no JavaScript framework, no library:
<!-- The HTML -->
<button popovertarget="menu" id="trigger">Open menu</button>
<div id="menu" popover>
<a>Profile</a>
<a>Settings</a>
<a>Sign out</a>
</div>
#trigger { anchor-name: --trigger; }
#menu {
position-anchor: --trigger;
position-area: bottom span-right; /* below, aligned to right */
position-try-fallbacks: top span-right, bottom span-left;
margin-top: 4px;
}
The native popover attribute handles open/close, click-outside-to-dismiss, and focus management. CSS Anchor Positioning handles placement. Zero JavaScript. The browser does everything Tippy and Floating-UI did combined.
07Conditional display — anchor-based visibility
A subtle but powerful feature: you can use anchor() inside other CSS properties, not just position. For example, sizing a tooltip to match its anchor's width:
.tooltip {
position-anchor: --btn;
left: anchor(left);
width: anchor-size(width); /* same width as anchor */
}
anchor-size() exposes the anchor's dimensions. You can use it for sizing, but also for offsets, margins, anything that takes a length. This is how you build select-style dropdowns where the menu matches the trigger's width.
08Firefox fallback strategy
Firefox is still implementing as of mid-2026. For sites that need to support Firefox today, the right approach is progressive enhancement: ship the CSS, accept that Firefox users get a static (un-flipped) positioning. The functionality still works; only the overflow handling degrades. For most tooltip cases this is acceptable.
@supports (anchor-name: --x) {
/* Anchor-positioning available — use the modern path */
}
@supports not (anchor-name: --x) {
/* Fallback: position manually or load Floating-UI */
}
∞What we're getting back
Every modern app ships at least one positioning library — often three, since teams add Tippy on top of Floating-UI on top of Popper for legacy reasons. Anchor Positioning collapses all of it into a CSS feature that ships with the browser, runs on the GPU, and handles edge cases the libraries used to charge complexity for.
The bigger pattern: features that started as JavaScript necessity are graduating to CSS primitives. Container queries killed responsive-component libraries. View Transitions are killing animation libraries. Anchor Positioning is killing tooltip libraries. The web platform is reclaiming territory from npm. We should be paying attention.