CSS anchor positioning is Baseline: tooltips and menus without JavaScript
Anchor positioning is Baseline since Firefox 147. Tie a tooltip or menu to a button in pure CSS, let position-try-fallbacks dodge screen edges, and fall back safely.
- Since January 2026 anchor positioning is Baseline: Chrome/Edge 125, Safari 26 and Firefox 147
- Three parts: anchor-name on the button, position-anchor on the floating box, and position-area for which side it goes
- A popover opened with popovertarget is implicitly anchored to its button, so a menu needs zero lines of JavaScript
- position-try-fallbacks tries backup positions in order until one fits, replacing the placement code that used to need a library
Every site has tooltips, kebab menus or dropdowns that must appear next to a button and get out of the way when that button sits near the edge of the screen. Until now that meant JavaScript: measure the button, compute coordinates, and listen for scroll and resize. CSS anchor positioning does it in pure CSS, and since January 2026 it works in every major browser.
Tested in Chromium 152 at 375 px and 800 px wide, with positions measured using getBoundingClientRect. Browser support checked against MDN, caniuse and web.dev as of October 2026.
Status as of October 2026
| Browser | Supported since |
|---|---|
| Chrome / Edge | 125 |
| Safari (macOS, iOS) | 26.0 |
| Firefox | 147 (January 2026) |
Firefox 147 was the last to arrive, which made the feature Baseline newly available in January 2026. One caveat: caniuse still marks several browsers “partial” because some sub-features are not equally supported. The parts used here (anchor-name, position-anchor, position-area, position-try-fallbacks, anchor-size()) are the core that works; check newer sub-features in the compat tables one by one.
The three parts
anchor-namenames the element to anchor to. Names start with--position-anchoron the floating box says which name it follows. The box must beposition: absoluteorfixed- Placement, two ways:
position-area, a 3×3 grid around the anchor (top,bottom span-right, …), or theanchor()function insidetop/leftwhen you need pixel control
anchor-size(width) reads the anchor’s size, for example to make a menu at least as wide as its button.
A tooltip in pure CSS
<button class="help" aria-describedby="tip1">?</button>
<span class="tip" id="tip1" role="tooltip">Drafts are kept for 30 days</span>
.help { anchor-name: --help; }
.tip {
position: absolute;
position-anchor: --help;
position-area: top; /* above the button, centred */
margin-bottom: 8px;
position-try-fallbacks: flip-block; /* no room above: go below */
width: max-content;
max-width: 240px;
opacity: 0;
pointer-events: none;
}
.help:hover + .tip,
.help:focus-visible + .tip { opacity: 1; }
Measured: with the button mid-screen (x 416–439, y 216–237) the tooltip sits above it, bottom edge at y 208, 8 px away as the margin says, centred exactly on the button at x 427.5. Move the button to the top edge (y 18–39) and the tooltip flips below on its own, to y 47 = 39 + 8; the margin flips too.
aria-describedby makes screen readers read the tooltip when the button gets focus, and :focus-visible shows it to keyboard users. Phones have no hover, so nothing essential should live only in a tooltip.
A popover menu: zero JavaScript
The Popover API opens and closes a box with the popovertarget attribute, and when opened that way the browser makes the button the popover’s implicit anchor. No anchor-name needed.
<button popovertarget="actions">Actions</button>
<div popover id="actions" class="menu">
<button>Edit</button>
<button>Duplicate</button>
<button>Delete</button>
</div>
[popover].menu {
margin: 0;
inset: auto; /* clear the default that centres popovers */
position-anchor: auto; /* use the implicit anchor from popovertarget */
position-area: bottom span-right; /* below, starting at the left edge, growing right */
position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
min-width: anchor-size(width);
margin-top: 4px;
}
You get open and close, Esc to dismiss, click-outside to dismiss, and placement, with no JavaScript.
Worked example: how the browser picks a fallback
position-try-fallbacks is a list of backup positions. The browser uses the main position first; if it overflows, it tries the next in order and takes the first that fits entirely on screen. If none fits, it goes back to the main position.
Measured on a 375 × 640 px screen, with the Actions button in the bottom-right corner (x 299–359, y 599–620) and an 82 × 79 px menu 4 px from the button:
| Try | Position | Vertical | Horizontal | Result |
|---|---|---|---|---|
| main | below, growing right | 624–703, past 640 | 299–381, past 375 | overflows |
flip-block | above, growing right | 516–595 | 299–381, past 375 | overflows |
flip-inline | below, growing left | 624–703, past 640 | 277–359 | overflows |
flip-block flip-inline | above, growing left | 516–595 | 277–359 | fits |
The browser actually placed it at x 277–359, y 516–595, the last row: 599 − 4 − 79 = 516 and 359 − 82 = 277. With the button moved to the top-right corner, the menu went below (y 74) but grew left (x 277): flip-inline.
Always end the list with
flip-block flip-inline. A button in a corner needs both axes flipped, and without that option the browser falls back to the main position, off screen.
For anything more elaborate (say, to the left with a capped width), define your own with @position-try --name { ... } and add the name to the list.
Hide it when the anchor scrolls away
If the button lives in a scrolling box and scrolls out of view, the tooltip can be left floating. Use position-visibility:
.tip { position-visibility: anchors-visible; } /* hide when the anchor is fully hidden */
The other value, no-overflow, hides the box once it starts to overflow the screen or its containing block.
A fallback for older browsers
Some users are still on browsers older than the table above, such as iPhones not yet on iOS 26. Use @supports so the UI still works, even if it looks plainer:
@supports not (anchor-name: --a) {
[popover].menu { inset: 0; margin: auto; } /* show centred, like a dialog */
}
The Popover API itself is more widely supported than anchor positioning, so the menu still opens and closes; it just does not sit next to the button.
Common mistakes
- Not clearing the popover defaults. Browsers give popovers
inset: 0andmargin: auto; leave those and the box will not attach to the button - Old names from 2024 tutorials.
inset-areais nowposition-areaandposition-try-optionsis nowposition-try-fallbacks; the old names no longer work - An anchor with
display: noneorvisibility: hidden. It cannot be anchored to, and the box lands in the wrong place - The same
anchor-namein many places, such as a button in every table row. The box anchors to the last one on the page; use implicit popover anchors oranchor-scopeto limit the scope - Only
flip-block. A button in a screen corner still overflows sideways - Not testing at phone width. Fallbacks only kick in when space runs out, so look at 375 px, as in letting an AI check the mobile layout
Sources
- anchor-name — MDN Web Docs
- Using CSS anchor positioning — MDN Web Docs
- Fallback options and conditional hiding for overflow — MDN Web Docs
- position-anchor — MDN Web Docs
- New to the web platform in January 2026 — web.dev
- Anchor positioning syntax changes — Chrome for Developers
- CSS Anchor Positioning — caniuse.com
FAQ
Do I still need Floating UI or Popper?
For everyday tooltips, menus and dropdowns, CSS now does the job. A library still makes sense if you must look identical on browsers older than the versions listed here, or if existing code leans heavily on library-specific features.
What do older browsers show?
The absolutely or fixed-positioned box falls back to its normal inset position, usually in the wrong place. Write a fallback in @supports not (anchor-name: --a), for example showing the popover menu centred like a dialog.
Older tutorials use inset-area and position-try-options. Do those work?
Those are pre-standard names. Chrome renamed them to position-area (Chrome 129) and position-try-fallbacks (Chrome 128). Use only the new names.
Commands and settings were tested on sample systems. Try them on a test machine before production.