CSS Anchor Positioning: How to Build Tooltips and Popovers Without JavaScript

CSS Anchor Positioning is a browser-native feature that lets you position any absolutely-placed element relative to another element in the document - no JavaScript required. Using the anchor() function, position-anchor property, and @position-try rules, you can build fully interactive tooltips, dropdown menus, and context menus in pure CSS and HTML. As of early 2026, it works in Chrome 125+, Firefox 132+, and Safari 18.2+, covering roughly 91% of browser traffic. Combined with the HTML popover attribute (Baseline 2024), you get show/hide toggling, keyboard dismissal, and top-layer stacking for free. The JavaScript tooltip library is effectively dead for most use cases.
The Problem CSS Anchor Positioning Solves
Before CSS Anchor Positioning existed, placing a tooltip next to a button required JavaScript. The standard recipe looked something like this: call getBoundingClientRect() on the trigger element, add scroll offsets, then set position: fixed with computed top and left values on the tooltip. You then had to recalculate on every scroll event, every resize event, and every orientation change on mobile. Libraries like Floating UI
(formerly Popper.js) exist entirely because this problem is surprisingly hard to get right.
The core difficulty is that position: absolute resolves relative to the nearest positioned ancestor, not relative to the trigger element. If the tooltip and its trigger button are siblings in the DOM (which they usually need to be for accessibility), the tooltip has no CSS-native way to know where the button is. Add a wrapper div somewhere up the component tree, or change an ancestor’s position property, and the tooltip drifts to the wrong spot. Every frontend developer has debugged this at least once.
CSS Anchor Positioning fixes this by introducing a direct relationship between two elements. You mark one element as an “anchor” using anchor-name: --my-anchor, and another element references it with position-anchor: --my-anchor. The positioned element can then use the anchor() function in its position values - writing top: anchor(bottom) literally means “my top edge should align with my anchor’s bottom edge.” The browser handles all the coordinate math internally during layout, with zero JavaScript overhead.
Browser support in 2026 is solid. Chrome and Edge have had full support since version 125. Firefox landed full support without flags in version 132. Safari 18.2+ supports the core anchor() function and position-anchor, though @position-try (the flip behavior) requires Safari 18.4+. For Safari 18.2-18.3 users, you get correct positioning but not automatic viewport flipping - a reasonable degradation.
There are still cases where JavaScript positioning makes sense. Complex multi-level nested menus with dynamically loaded content, tooltips inside shadow DOM with cross-origin constraints, and virtualized lists where the anchor element might be unmounted from the DOM all push you back toward a JS solution. But for the vast majority of tooltip and dropdown use cases - the ones that account for 90% of Floating UI installs - CSS Anchor Positioning handles everything.
Anchor positioning and the HTML popover attribute solve different halves of the same problem. The popover attribute handles visibility toggling, Escape key dismissal, light-dismiss (clicking outside), and rendering in the browser’s top layer. CSS Anchor Positioning handles where the popup appears on screen. Together, they cover everything that a JavaScript tooltip library used to provide.
Core Syntax: anchor-name, position-anchor, and anchor()
The CSS Anchor Positioning API has three core building blocks that cover the full range of tooltip and dropdown positioning needs.
Defining an Anchor
To mark an element as an anchor, add the anchor-name property with a dashed-ident value:
.trigger-button {
anchor-name: --button-anchor;
}Anchor names use the -- prefix, just like CSS custom properties. You can define multiple anchors on different elements throughout the page, each with a unique name. Any element can be an anchor - buttons, links, table cells, icons, whatever you need.
Referencing an Anchor
On the element you want to position (the tooltip, dropdown, or popover), set position-anchor to reference the anchor name, and make sure the element has position: absolute or position: fixed:
.tooltip {
position: absolute;
position-anchor: --button-anchor;
}The positioned element is now tethered to the anchor element regardless of where they sit in the DOM tree. They do not need to be siblings or have any particular parent-child relationship.
The anchor() Function
The anchor() function goes inside position properties (top, right, bottom, left) to reference specific edges or the center of the anchor element:
.tooltip {
position: absolute;
position-anchor: --button-anchor;
top: anchor(bottom); /* tooltip's top = anchor's bottom */
left: anchor(center); /* tooltip's left = anchor's center */
transform: translateX(-50%);
margin-top: 4px;
}This places the tooltip directly below the button, centered horizontally. The anchor(center) value resolves to the midpoint of the anchor on that axis. You can mix anchor() with calc() for more precise control - left: calc(anchor(center) - 50%) is another way to center the tooltip.
Sizing with anchor-size()
The anchor-size() function lets you size a positioned element based on its anchor’s dimensions:
.dropdown-menu {
width: anchor-size(width);
}This makes the dropdown exactly as wide as its trigger button - a common pattern for select-style dropdowns. The function accepts width, height, inline, block, self-inline, and self-block as arguments.
Logical Properties
For internationalized UIs that need to handle right-to-left text or vertical writing modes, use logical property equivalents instead of physical ones:
.tooltip {
inset-block-start: anchor(block-end); /* equivalent to top: anchor(bottom) */
inset-inline-start: anchor(inline-start); /* equivalent to left: anchor(left) */
}Complete Tooltip Example
Here is a working tooltip in pure CSS:
.trigger {
anchor-name: --tip-anchor;
}
.tooltip {
position: absolute;
position-anchor: --tip-anchor;
top: anchor(bottom);
left: anchor(center);
transform: translateX(-50%);
margin-top: 4px;
background: #1a1a2e;
color: #fff;
padding: 6px 10px;
border-radius: 4px;
font-size: 0.85rem;
white-space: nowrap;
}That gets you a working tooltip with no event listeners, no getBoundingClientRect() calls, and no resize observers. The browser handles all of it during the layout phase.
Flip Behavior with @position-try and position-try-fallbacks
A tooltip anchored below a button works fine until the button is near the bottom of the viewport. At that point, the tooltip gets clipped or causes overflow. The traditional fix requires a JavaScript resize observer that detects available space and flips the tooltip above the trigger. CSS Anchor Positioning handles this natively with @position-try rules.
Defining Fallback Positions
A @position-try block defines an alternate positioning strategy:
@position-try --flip-above {
top: auto;
bottom: anchor(top);
margin-top: 0;
margin-bottom: 4px;
}This fallback says: instead of placing the tooltip below the anchor, place it above. The position-try-fallbacks property on the positioned element lists these fallbacks in preference order:
.tooltip {
position: absolute;
position-anchor: --tip-anchor;
top: anchor(bottom);
left: anchor(center);
transform: translateX(-50%);
margin-top: 4px;
position-try-fallbacks: --flip-above;
}The browser renders the tooltip with the base position first. If the tooltip would overflow the viewport, it tries --flip-above. If that also overflows, it falls back to the base position (clipped). You can chain multiple fallbacks:
position-try-fallbacks: --flip-above, --shift-right, --shift-left;Built-in Flip Keywords
For the most common case - mirroring the position on one axis - there are shorthand keywords that save you from writing custom @position-try blocks:
.tooltip {
position-try-fallbacks: flip-block;
}flip-block automatically generates the mirrored version of the base position on the block axis (top becomes bottom, bottom becomes top). flip-inline does the same on the inline axis. For most tooltip implementations, flip-block is all you need.
Controlling Fallback Selection with position-try-order
The position-try-order property changes how the browser picks between fallbacks:
.dropdown-menu {
position-try-order: most-block-size;
position-try-fallbacks: flip-block;
}most-block-size tells the browser to prefer whichever position gives the element the most available vertical space. This is particularly useful for dropdown menus that may contain a variable number of items - the menu opens in whichever direction has more room.
Production-Ready Flip Configuration
A robust tooltip setup in production typically needs three to four fallbacks: the base position (below-center), a block flip (above-center), and shift fallbacks for buttons near the left or right viewport edges:
@position-try --flip-above {
top: auto;
bottom: anchor(top);
margin-bottom: 4px;
}
@position-try --shift-right {
left: anchor(left);
transform: none;
}
@position-try --shift-left {
left: auto;
right: anchor(right);
transform: none;
}
.tooltip {
position: absolute;
position-anchor: --tip-anchor;
top: anchor(bottom);
left: anchor(center);
transform: translateX(-50%);
margin-top: 4px;
position-try-fallbacks: --flip-above, --shift-right, --shift-left;
}This covers the vast majority of viewport scenarios without a single line of JavaScript.
Combining Anchor Positioning with the HTML popover Attribute
The HTML popover attribute (Baseline 2024, supported in all major browsers) handles the other half of the popup problem: visibility toggling, keyboard interactions, and stacking context. When you pair it with CSS Anchor Positioning, you get a fully declarative, accessible popup system.
Popover Basics
Add the popover attribute to any element to make it a popover. Add popovertarget to a button to create the toggle control:
<button popovertarget="my-tooltip">Hover me</button>
<div id="my-tooltip" popover>This is a tooltip</div>The browser handles Escape key dismissal, light dismiss (clicking anywhere outside the popover), and renders the popover in the top layer
- above all other content, regardless of z-index values elsewhere in the document. No more z-index: 99999 hacks.
Connecting Popover to Anchor
Combine the popover HTML with anchor positioning CSS:
<button popovertarget="tip" class="trigger">Info</button>
<div id="tip" popover class="tooltip">Helpful information here</div>.trigger {
anchor-name: --trigger;
}
.tooltip {
position-anchor: --trigger;
top: anchor(bottom);
left: anchor(center);
transform: translateX(-50%);
margin: 0;
margin-top: 4px;
position-try-fallbacks: flip-block;
}Clicking the button toggles the tooltip. Pressing Escape closes it. Clicking outside closes it. The tooltip appears directly below the button and flips above if there is not enough space - all without a single line of JavaScript.
Backdrop Customization
Popovers rendered in the top layer can display a backdrop overlay:
[popover]::backdrop {
background: rgba(0, 0, 0, 0.3);
}For a frosted glass effect, add backdrop-filter: blur(4px). This works well for modal-style dialogs that use anchor positioning to stay attached to their trigger element.
Accessibility
The popover attribute with popover=auto provides built-in dialog semantics. Combine with aria-labelledby to give screen readers a proper title:
<button popovertarget="info-panel" aria-expanded="false">Details</button>
<div id="info-panel" popover aria-labelledby="panel-title">
<h3 id="panel-title">Additional Details</h3>
<p>Content here...</p>
</div>Keyboard users open the popover with Enter or Space on the trigger button and close with Escape. Focus management is handled by the browser.
The popover=hint Variant
Chrome 126+ introduced popover=hint, a variant designed specifically for tooltips that show on hover or focus and dismiss on mouseout. As of early 2026, it is still behind a flag in some browsers, but the specification is maturing quickly:
<button popovertarget="hover-tip">?</button>
<div id="hover-tip" popover="hint">Explanation text</div>Once browser support catches up, popover=hint will cover the hover-triggered tooltip case that currently still needs a small amount of JavaScript.
Full Dropdown Menu Example
Here is a complete dropdown menu in about 10 lines of HTML with no JavaScript:
<button popovertarget="nav-menu" class="menu-trigger">Menu</button>
<ul id="nav-menu" popover class="dropdown">
<li><a href="/about">About</a></li>
<li><a href="/blog">Blog</a></li>
<li><a href="/contact">Contact</a></li>
</ul>.menu-trigger {
anchor-name: --nav-trigger;
}
.dropdown {
position-anchor: --nav-trigger;
top: anchor(bottom);
left: anchor(left);
margin: 0;
margin-top: 2px;
width: anchor-size(width);
min-width: 150px;
position-try-fallbacks: flip-block;
}The dropdown matches the trigger button’s width (but has a minimum of 150px), appears directly below it, and flips above when near the viewport bottom. The browser handles open/close toggling, keyboard navigation, and stacking.
Progressive Enhancement and Fallback Strategies
Despite broad 2026 support, Safari’s partial @position-try support in versions 18.2-18.3 and older browser installs on enterprise machines mean fallbacks are still necessary for production sites.
Feature Detection with @supports
Wrap anchor positioning CSS in a @supports block so unsupported browsers fall back gracefully:
/* Base fallback: simple static positioning */
.tooltip {
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
}
/* Enhanced: use anchor positioning when supported */
@supports (anchor-name: --a) {
.tooltip {
position-anchor: --tip-anchor;
top: anchor(bottom);
left: anchor(center);
transform: translateX(-50%);
position-try-fallbacks: flip-block;
}
}Browsers without anchor positioning support get a tooltip that appears below its parent element using traditional CSS. Not perfect, but functional.
Safari 18.2-18.3 Handling
Safari 18.2 and 18.3 support anchor() and position-anchor but not @position-try. The practical approach is to write your base position outside any @position-try blocks - Safari will render the tooltip in the correct position, just without automatic viewport flipping. Chrome, Firefox, and Safari 18.4+ get the enhanced flip behavior. This is a graceful degradation that most users will never notice.
JavaScript Polyfill Option
The community-maintained @oddbird/css-anchor-positioning
polyfill provides anchor() and @position-try support for browsers without native implementations. It is roughly 8KB gzipped and can be loaded conditionally:
<script>
if (!CSS.supports('anchor-name: --a')) {
const script = document.createElement('script');
script.src = 'https://unpkg.com/@oddbird/css-anchor-positioning';
document.head.appendChild(script);
}
</script>This way, modern browsers pay zero JavaScript cost, and only legacy browsers download the polyfill.
Floating UI as a Fallback
For projects that must support Safari versions older than 18.2 with precise viewport-edge logic, Floating UI 3.0 (the successor to Popper.js) works as a targeted polyfill. Use CSS Anchor Positioning as the primary implementation and load Floating UI only for legacy browsers. This keeps the modern code path clean and avoids shipping unnecessary JavaScript to 91% of your users.
Performance Comparison
CSS Anchor Positioning runs entirely in the browser’s layout engine. There is no JavaScript execution, no requestAnimationFrame loops, and no layout thrashing from getBoundingClientRect() calls. Floating UI’s computePosition() runs in the microtask queue and triggers layout reflow each time it recalculates. On low-end mobile devices, the difference is measurable - CSS-based positioning produces smoother scroll performance and lower battery consumption. On desktop, both approaches feel instant, but the CSS approach has the advantage of zero bundle size impact.
Testing Your Implementation
Test anchor positioning across browsers using this checklist:
- In Chrome DevTools, resize the viewport to trigger flip behavior and verify
@position-tryfallbacks activate correctly - Use Safari Technology Preview to test Safari 18.4+
@position-trysupport specifically - Check Firefox Developer Edition to verify full feature support without flags
- On mobile devices, use BrowserStack or real devices to test touch interactions and viewport flipping on scroll
The CSS Anchor Positioning specification is stable and well-supported enough for production use in 2026. For new projects, there is no reason to reach for a JavaScript positioning library as a first choice. Write your tooltips and dropdowns in CSS, add @position-try for viewport flipping, pair with the popover attribute for interactivity, and ship. The browser does the rest.