Skip to content

Dropping Floating UI for Native CSS Anchor Positioning

The CSS anchor-name and position-anchor properties tie one element's position to another, replacing Floating UI and Popper.js for common tooltip cases.

· · 7 min read

Updated: October 16, 2026

A group of white and black directional signs

Quick Take

`anchor-name` on a trigger element and `position-anchor` plus `anchor()` on a positioned element tie the two together natively in CSS, no JavaScript position calculation required. Combined with the HTML `popover` attribute for top-layer rendering and light-dismiss, this covers the common tooltip and dropdown pattern that Popper.js and Floating UI existed to solve, in browsers that support it.

Run this yourself. The script that produced the bundle figures below: anchor-positioning-bundle-cost/ in the Coding Dunia code-examples repo.

CSS anchor positioning is a set of CSS properties, anchor-name, position-anchor, and the anchor() function, that tie one element's position to another element's rendered location without any JavaScript calculation. Every tooltip and dropdown library exists to solve one problem: keep element B positioned relative to element A, correctly, even as the page scrolls, resizes, or the anchor moves. That's a surprisingly involved calculation once you account for viewport edges, and it's exactly the calculation CSS anchor positioning now does natively.

.trigger-button {
  anchor-name: --my-tooltip-anchor;
}

.tooltip {
  position: fixed;
  position-anchor: --my-tooltip-anchor;
  top: anchor(bottom);
  left: anchor(center);
  translate: -50% 8px;
}
<button class="trigger-button">Hover me</button>
<div class="tooltip" popover>Helpful tooltip text</div>

anchor-name gives the button a named anchor reference. position-anchor on the tooltip points at that name, and anchor(bottom)/anchor(center) compute the tooltip's top/left relative to the anchor's actual rendered position, bottom edge and horizontal center respectively. This recalculates automatically on scroll and resize, the same behavior Floating UI's autoUpdate() exists to provide manually. Per MDN, the anchor name itself is a dashed-ident, the same custom-property-style syntax CSS variables use, which means one anchor element can be referenced by several positioned elements at once, a single button anchoring both a tooltip and a badge, for example, without any extra JavaScript wiring.

How Do Fallback Positions Work With @position-try?

.tooltip {
  position: fixed;
  position-anchor: --my-tooltip-anchor;
  top: anchor(bottom);
  left: anchor(center);
  translate: -50% 8px;
  position-try-fallbacks: flip-block;
}

@position-try flip-block {
  top: anchor(top);
  bottom: unset;
  translate: -50% -8px;
}

position-try-fallbacks: flip-block tells the browser: if the tooltip would overflow the viewport in its preferred position (below the anchor), try the flip-block fallback instead (above the anchor). Several built-in try-tactic keywords exist (flip-block, flip-inline, flip-start) covering the common flip cases without a custom @position-try block at all, worth checking before writing a manual fallback. Per the spec, you can also list multiple fallbacks in priority order, position-try-fallbacks: flip-block, flip-inline;, and the browser evaluates them one at a time until it finds a position that fits, which is the same collision-avoidance logic Floating UI's autoPlacement() middleware implements in roughly two hundred lines of JavaScript.

How Do You Combine Anchor Positioning With popover?

<button popovertarget="user-menu" class="menu-trigger">Account</button>
<div id="user-menu" popover class="dropdown-menu">
  <a href="/profile/">Profile</a>
  <a href="/settings/">Settings</a>
  <a href="/logout/">Log out</a>
</div>
.menu-trigger {
  anchor-name: --user-menu-anchor;
}

.dropdown-menu {
  position-anchor: --user-menu-anchor;
  top: anchor(bottom);
  right: anchor(right);
  margin-top: 4px;
}

The popover attribute is an HTML global attribute that gives any element top-layer rendering, meaning it paints above the rest of the page regardless of stacking context, along with built-in light-dismiss and keyboard accessibility, all without JavaScript. popovertarget on the button wires up open/close behavior, focus handling, and light-dismiss (clicking outside closes it) entirely through HTML attributes, no JavaScript addEventListener needed for that part either. anchor-name/position-anchor handle where it appears. Together, this is a complete dropdown menu, positioning, top-layer rendering, dismiss behavior, and keyboard accessibility, without a single line of JavaScript or a dependency, replacing what used to take two or three separate npm packages working in concert.

How Do You Size the Positioned Element to Fit?

.dropdown-menu {
  position-anchor: --user-menu-anchor;
  top: anchor(bottom);
  right: anchor(right);
  max-height: anchor-size(height); /* size relative to the anchor's own height, if useful */
  max-width: 320px;
  overflow-y: auto;
}

anchor-size() reads a dimension from the anchor element itself, useful for a dropdown that should match its trigger's width exactly (width: anchor-size(width);), rather than having a fixed or content-based width unrelated to the button that opened it. According to Chrome for Developers, anchor-size() accepts width, height, inline-size, block-size, and both self and inline dimensions, so a dropdown can also cap its own height at the viewport space actually available near the anchor, not just match the trigger's width.

What Is the Browser Support and Fallback Story?

Anchor positioning shipped in Chrome and Edge in 2024, with Firefox and Safari following through 2025 and into 2026. For the remaining gap, this is a feature where a JavaScript fallback still matters more than most CSS features covered elsewhere in this series, an unsupported browser doesn't gracefully degrade to "no positioning," it renders the tooltip at its default static position, likely overlapping other content. Feature-detect with @supports (anchor-name: --a) and load Floating UI conditionally only for browsers that fail that check, rather than assuming universal support the way you reasonably could with :has() or scroll-driven animations by 2026.

A quick three-step check before shipping anchor positioning without a fallback:

  1. Confirm your actual analytics show the browser mix you're targeting, don't assume from caniuse alone.
  2. Wrap the CSS in @supports (anchor-name: --a) so unsupported browsers can be routed to a JavaScript fallback instead of silently rendering overlapping content.
  3. Load Floating UI or a similar library conditionally, only inside the unsupported branch, so supported browsers never pay for the extra dependency.

Anchor positioning is the rare 2026 CSS feature where an unsupported browser does not gracefully degrade, it renders the positioned element at its default static location, likely overlapping other content, which is why feature-detecting with @supports before dropping a JavaScript fallback still matters here.

Share this Post on X Bluesky

How Does This Compare to the Old Approach?

Four common jobs, tooltip attachment, scroll recalculation, viewport-overflow handling, and dropdown dismiss behavior, used to each need their own piece of JavaScript. The table below lines up what each one cost before against what it costs with anchor positioning and popover combined.

TaskBeforeWith anchor positioning
Tooltip stays attached to its triggerFloating UI / Popper.js, JS position calcanchor-name + anchor()
Recalculate on scroll/resizeManual autoUpdate() listenerAutomatic, native
Flip to avoid viewport overflowManual collision detection logicposition-try-fallbacks
Dropdown open/close, focus, dismissCustom JS or a UI libraryHTML popover attribute

So What Does That Dependency Actually Weigh?

"Removes a dependency" is the kind of claim that sounds like a win until someone asks how big the dependency was. So I weighed it. Not computePosition on its own, which would flatter the CSS side unfairly, but the tooltip nobody would ship without: offset for the gap, flip to move above the trigger when there's no room below, shift to keep it inside the viewport, arrow for the triangle. Bundled with esbuild, minified, then compressed.

@floating-ui/dom 1.8.0, tooltip buildBytes
minified15,098 B14.74 kB
minified + gzip6,115 B5.97 kB
minified + brotli5,529 B5.40 kB

Measured 2026-08-24 on Node 20.20.0, esbuild 0.28.2. The CSS version ships 0 B of JavaScript.

Six kilobytes gzipped isn't going to sink your page, and I'd rather say that than pretend otherwise. Tree-shaking also means your figure moves with your middleware list, so a tooltip that never flips comes in lighter than this one. The reason to reach for anchor() isn't the six kilobytes, it's that a dependency you deleted has no upgrade path, no breaking change in its next major, and no autoUpdate() listener you have to remember to tear down.

What Should You Take Away From This?

Anchor positioning doesn't add a capability CSS never had a workaround for, it removes the JavaScript dependency that workaround required. For a straightforward tooltip or dropdown, anchor-name/anchor() combined with the popover attribute covers what Floating UI existed to solve, with the caveat that support isn't universal enough yet to drop the JavaScript fallback for every project without checking your actual browser support requirements first.

Frequently Asked Questions

What does CSS anchor positioning actually do?
It lets one element (the positioned element, like a tooltip) declare its position relative to a different element (the anchor, like a button) using CSS alone, via anchor-name on the anchor and position-anchor plus inset properties referencing anchor() on the positioned element. Before this, keeping a tooltip attached to a button that could be anywhere on the page, and recalculating that position on scroll or resize, required a JavaScript library computing coordinates on every relevant event.
Does anchor positioning replace the popover attribute?
No, they solve different problems and are commonly used together. The HTML popover attribute handles top-layer rendering, focus management, and light-dismiss behavior (closing when you click outside), CSS anchor positioning handles where the popover appears relative to its trigger. A popover without anchor positioning still needs manual position calculation; anchor positioning without popover still needs manual top-layer/focus handling. Combined, they cover the whole tooltip/dropdown pattern natively.
What happens when there's no room for the tooltip in its preferred position?
The @position-try at-rule lets you define fallback positions, and the browser automatically switches to one when the preferred position would overflow the viewport, a tooltip that prefers to open above its anchor but is too close to the top of the screen can fall back to opening below, without any JavaScript resize/scroll listener recalculating this manually.