Position tooltips, popovers, and dropdowns relative to any element — without a single line of JavaScript. Fully native, declarative, and supported across every major browser.
For decades, positioning a tooltip next to a button, a dropdown below a menu item, or a popover near a link required JavaScript — Popper.js, Floating UI, Tippy.js, or hand-rolled positioning logic. Every scroll, resize, and DOM mutation needed recalculation.
CSS Anchor Positioning changes this fundamentally. It's a native CSS module that lets you tether any positioned element to another element — an "anchor" — using only CSS. The browser handles positioning, overflow detection, and fallback placement automatically.
In this guide, I'll walk through everything you need to use CSS Anchor Positioning in production: from basic tooltip positioning to advanced patterns with the Popover API, sizing, fallbacks, and migration from JavaScript libraries.
CSS Anchor Positioning has reached full cross-browser support as part of the Interop 2026 initiative. Here's the current status:
| Browser | Version | Status |
|---|---|---|
| Chrome | 125+ | Full support |
| Edge | 125+ | Full support |
| Safari | 26+ | Full support |
| Firefox | 147+ | Full support |
| Opera | 111+ | Full support |
| Samsung Internet | 27+ | Full support |
All major browsers shipped stable support by May 2026. Firefox 147 was the final addition, completing the cross-browser picture. As an Interop 2026 focus area, the feature underwent rigorous cross-browser testing to ensure consistent behavior.
No polyfills needed. CSS Anchor Positioning works in every current browser. For critical UIs that must support very old browsers (Chrome <125, Safari <26, Firefox <147), provide a Popper.js or Floating UI fallback — but this is rarely necessary for modern web applications.
CSS Anchor Positioning has three foundational properties you'll use in every project:
anchor-name — Defining the Anchor
First, mark an element as an anchor using anchor-name. The value is a dashed ident
(like a CSS custom property name) that other elements reference:
/* Define the anchor element */
.trigger-button {
anchor-name: --tooltip-anchor;
}
The anchor itself is invisible in terms of positioning — it's just a reference point. Any element can be an anchor: buttons, links, images, even text nodes wrapped in a span.
position-anchor — Attaching to an Anchor
On the element you want to position (the "target" or "anchored element"), set
position-anchor to reference the anchor:
/* Position this element relative to the anchor */
.tooltip {
position: absolute;
position-anchor: --tooltip-anchor;
}
Important: The target element must have a position value other
than static (e.g., absolute, fixed, or relative).
Otherwise, anchor positioning has no effect.
position-area — Where to Place It
position-area is the most intuitive way to position the target relative to the anchor.
It uses a grid-like model: the anchor is at the center, and you specify which "area" around it
the target should appear in:
.tooltip {
position: absolute;
position-anchor: --tooltip-anchor;
position-area: top center;
/* Target appears centered above the anchor */
}
.tooltip-right {
position: absolute;
position-anchor: --tooltip-anchor;
position-area: center right;
/* Target appears vertically centered to the right */
}
The position-area value has two components separated by a space:
top, center, bottomleft, center, right
The possible position-area values span a 3×3 grid: top left,
top center, top right, center left,
center center (covers the anchor), center right,
bottom left, bottom center, bottom right.
Here's a fully working tooltip using nothing but CSS — no JavaScript required:
<button class="anchor">
Hover me
</button>
<div class="tooltip">I'm a pure CSS tooltip!</div>
<style>
.anchor {
anchor-name: --tip-anchor;
position: relative; /* containment context */
}
.tooltip {
position: absolute;
position-anchor: --tip-anchor;
position-area: top center;
position-try-fallbacks: bottom center, center right;
/* Styling */
background: #1a1a2e;
color: white;
padding: 0.5rem 1rem;
border-radius: 6px;
font-size: 0.85rem;
white-space: nowrap;
pointer-events: none;
opacity: 0;
transition: opacity 0.2s;
}
.anchor:hover + .tooltip,
.anchor:focus-visible + .tooltip {
opacity: 1;
}
</style>
The tooltip appears above the button on hover. If it overflows the viewport, the browser
automatically tries bottom center, then center right — all
declaratively, no JavaScript needed.
The position-try-fallbacks property is what makes CSS Anchor Positioning genuinely
production-ready. It defines a list of alternative positions the browser tries when the primary
position doesn't fit within the viewport:
.dropdown {
position: absolute;
position-anchor: --menu-anchor;
position-area: bottom left;
position-try-fallbacks:
top left,
bottom right,
top right,
flip-block,
flip-inline;
}
The browser evaluates each position in order and selects the first one where the entire target
element fits within the viewport. The flip-block and flip-inline
keywords are shorthand: flip-block mirrors the position across the block axis
(top ↔ bottom), while flip-inline mirrors across the inline axis (left ↔ right).
Pro tip: Start with flip-block and flip-inline as your
primary fallbacks. They cover the most common overflow scenarios in one line each. Add
specific position fallbacks only when you need precise control.
Dropdown menus are one of the best use cases for CSS Anchor Positioning. Here's a complete implementation that works without JavaScript positioning:
<nav>
<button class="menu-trigger" popovertarget="menu1">
Products
</button>
<div id="menu1" class="dropdown" popover>
<a href="#">Web Development</a>
<a href="#">Mobile Apps</a>
<a href="#">Consulting</a>
</div>
</nav>
<style>
.menu-trigger {
anchor-name: --menu-anchor;
}
.dropdown {
position: absolute;
position-anchor: --menu-anchor;
position-area: bottom left;
position-try-fallbacks:
top left,
flip-block;
/* Match the button's width */
width: anchor-size(width);
/* Styling */
border: 1px solid #e2e8f0;
border-radius: 8px;
background: white;
padding: 0.5rem;
box-shadow: 0 4px 16px rgba(0,0,0,0.1);
margin-top: 4px;
}
.dropdown a {
display: block;
padding: 0.5rem 1rem;
text-decoration: none;
color: #1a1a2e;
border-radius: 4px;
}
.dropdown a:hover {
background: #f1f5f9;
}
</style>
Key details: anchor-size(width) makes the dropdown match the trigger button's width,
position-try-fallbacks: top left, flip-block handles viewport overflow gracefully,
and the Popover API (popover + popovertarget) manages open/close and
top-layer rendering without JavaScript.
The Popover API and CSS Anchor Positioning are a perfect match. The Popover API handles top-layer rendering (elements appear above everything, including z-index stacks), focus trapping, light-dismiss (click outside closes the popover), and ESC key handling — while Anchor Positioning handles where the popover appears on screen.
<button popovertarget="notif-popover" style="anchor-name: --notif-anchor;">
Notifications (3)
</button>
<div id="notif-popover" popover
style="position: absolute;
position-anchor: --notif-anchor;
position-area: bottom right;">
<h4>New Notifications</h4>
<p>You have 3 unread messages.</p>
</div>
That's it. The popover opens when the button is clicked, appears anchored to the bottom-right of the button, and clicking outside or pressing ESC closes it. All native — no event listeners, no positioning calculations.
<button class="bell-btn" style="anchor-name: --bell;">
🔔
<span class="badge">3</span>
</button>
<style>
.badge {
position: absolute;
position-anchor: --bell;
position-area: top right;
/* Offset slightly from the exact top-right corner */
translate: 25% -25%;
background: #ef4444;
color: white;
font-size: 0.7rem;
padding: 0.1em 0.4em;
border-radius: 999px;
min-width: 1.2em;
text-align: center;
}
</style>
The translate property fine-tunes the position — here pulling the badge slightly
outside the bell icon's top-right corner, exactly where notification badges should appear.
anchor-size()
The anchor-size() function lets positioned elements size themselves relative to
their anchor. This is extremely useful for dropdowns, autocomplete menus, and context panels
that should match their trigger element's dimensions:
.autocomplete-popup {
position: absolute;
position-anchor: --search-anchor;
position-area: bottom left;
/* Same width as the search input */
width: anchor-size(width);
/* At least 100px tall, up to 300px based on anchor height */
max-height: anchor-size(height, 300px);
min-height: anchor-size(height, 100px);
}
/* With a named anchor reference */
.dropdown-panel {
position: absolute;
position-anchor: --btn-anchor;
width: anchor-size(--btn-anchor width, 200px);
}
The anchor-size() function takes two arguments:
width, height, block, or inline
You can also reference a specific anchor by name: anchor-size(--my-anchor width), which
is useful when an element is positioned relative to one anchor but sized relative to another.
For context menus that appear at the mouse cursor position, CSS Anchor Positioning can use
an implicit anchor created via the anchor() function with the --pointer
implicit anchor:
.context-menu {
position: fixed;
/* Position at the pointer location */
left: anchor(--pointer left);
top: anchor(--pointer top);
/* Ensure it stays visible */
position-try-fallbacks:
flip-block,
flip-inline,
top left;
}
Note: As of early 2026, implicit pointer anchoring (--pointer) is
still experimental and may not be available in all browsers. For production context menus, use
JavaScript mousedown / contextmenu events to set an explicit anchor
position via inline styles, or fall back to a JS-based solution.
If your project currently uses Popper.js or Floating UI, here's how to migrate to CSS Anchor Positioning. The migration is surprisingly simple for most use cases:
// JavaScript
import { createPopper } from '@popperjs/core';
const button = document.querySelector('.trigger');
const tooltip = document.querySelector('.tooltip');
const popperInstance = createPopper(button, tooltip, {
placement: 'top',
modifiers: [
{ name: 'flip', options: { fallbackPlacements: ['bottom', 'right'] } },
{ name: 'offset', options: { offset: [0, 8] } },
],
});
<style>
.trigger {
anchor-name: --trigger;
}
.tooltip {
position: absolute;
position-anchor: --trigger;
position-area: top center;
position-try-fallbacks: bottom center, center right;
margin-bottom: 8px; /* replaces offset */
}
</style>
In 90% of cases, you can replace Popper.js/Floating UI with a few lines of CSS. The remaining 10% — complex animations, drag-connected tooltips, portal-based rendering — may still benefit from a JS library, but the gap is closing fast as browsers ship more anchor positioning features.
| Feature | CSS Anchor Positioning | Popper.js / Floating UI |
|---|---|---|
| Bundle size | 0 KB (native) | ~6 KB (gzip) |
| Overflow detection | Automatic via position-try-fallbacks | Runtime calculation |
| Resize handling | Automatic (browser-native) | Event listeners + recalculation |
| Scroll handling | Automatic (re-positions on scroll) | Scroll listeners |
| Old browser support | Chrome 125+, Safari 26+, FF 147+ | IE11+ |
| Dynamic content | Automatic | with update() call |
| Complex animations | CSS transitions/animations | Built-in transitions |
| Setup complexity | Declarative (one CSS property) | JavaScript instantiation |
The most common mistake. Your target element needs position: absolute,
position: fixed, or position: relative. Without this, anchor
positioning silently does nothing.
If the anchor element has overflow: hidden or contain: paint,
the positioned element might get clipped. Consider moving the positioned element outside
the overflowing container, or use position: fixed instead of
position: absolute.
The anchor-name must be a dashed ident (e.g., --my-anchor) and
position-anchor must reference the exact same name. Typos are easy and
produce no error — the target simply won't position.
When using the Popover API, the popover attribute must be on the positioned
element (the tooltip/dropdown), not the anchor. The popovertarget attribute
goes on the button.
Chrome DevTools and Firefox DevTools both show anchor relationships in the Elements panel.
In Chrome, the positioned element displays an "anchor" badge — click it to see the anchor
connection highlighted. The Computed panel also shows which position-try-fallbacks
strategy is active.
CSS Anchor Positioning and CSS Container Queries are complementary, not competing, features. Container queries let a component style itself based on its parent container's size. Anchor positioning lets an element tether itself to another element's position. Together, they enable truly self-aware, context-responsive UI components.
For example, a card component might use Container Queries to adjust its layout at different sizes, while its tooltip uses Anchor Positioning to stay attached to the card's info icon regardless of where the card is in the page.
anchor-name and position the target with position-anchor and position-area. The browser handles overflow detection, fallback placement, and resize recalculation automatically.position-area defines the primary position (e.g., top center, bottom right). position-try-fallbacks specifies alternative positions the browser tries if the primary position overflows the viewport. The browser evaluates fallbacks in order and uses the first one that fits entirely on screen.popover attribute) can be anchored to its trigger button using position-anchor. The popover opens positioned relative to the button, appears in the top layer (above all z-index stacks), and the browser handles focus management, light-dismiss, and ESC key handling automatically.anchor-size() lets you size an element based on its anchor's dimensions. For example, width: anchor-size(width) makes a dropdown the same width as its trigger button. The function accepts an optional fallback: width: anchor-size(--anchor width, 200px). This is ideal for dropdown menus, autocomplete popups, and context panels that should align perfectly with their trigger.
CSS Anchor Positioning is one of those rare features that fundamentally simplifies how we build
UIs. No more npm-installing a positioning library for every project that has a tooltip or
dropdown. No more ResizeObserver + scroll event listener combos. Just
clean, declarative CSS that the browser executes efficiently.
I've been using CSS Anchor Positioning in production since Chrome 125. It has eliminated thousands of lines of JavaScript from client projects. If you're planning a new web application or refactoring an existing one, this is the right time to adopt it.
Need help implementing modern CSS patterns in your project? I build production web applications using the latest CSS and JavaScript standards — let's talk about your next project.
Need help implementing modern CSS patterns, building a web application, or migrating from JavaScript positioning libraries to native CSS? I'd love to help.