CSS Anchor Positioning Explained
Positioning one element relative to another — a tooltip next to a button, a dropdown below a trigger, a popover beside an icon — is one of the most common UI patterns on the web, and it has been one of the most annoying to implement correctly. For years, the standard approach has been to use JavaScript libraries like Popper.js (now Floating UI), which measure element positions, calculate coordinates, handle scroll offsets, manage viewport collision detection, and update positions on every resize and scroll event. These libraries are impressive feats of engineering, but they exist because CSS lacked the fundamental ability to position one element relative to another arbitrary element in the document.
CSS Anchor Positioning changes this. It is a new CSS specification that lets you declare a relationship between an "anchor" element and a "positioned" element entirely in CSS. The browser handles the coordinate math, scroll tracking, and viewport collision avoidance natively, in the rendering engine, at the speed of the layout pipeline. The result is tooltips, popovers, dropdowns, and other tethered UI elements that are simpler to build, more performant, and more reliable than their JavaScript-based counterparts.
This guide covers the full anchor positioning API: from declaring anchors and positioned elements, through the anchor() function and position-area property, to fallback positions with position-try-fallbacks and progressive enhancement strategies for non-supporting browsers. By the end, you will be able to build production-quality positioned UI with pure CSS.
The Problem Anchor Positioning Solves
To understand why anchor positioning matters, consider what it takes to build a tooltip with existing CSS. You need the tooltip to appear above (or below, or beside) a specific element, stay attached to it when the page scrolls, flip to the other side if it would overflow the viewport, and update its position if the page layout changes. Here is the traditional approach:
- Position the tooltip absolutely or fixed relative to an offset parent.
- Calculate the trigger element's bounding rectangle with
getBoundingClientRect(). - Compute the tooltip's coordinates based on the trigger's position, the tooltip's own dimensions, and the desired placement (top, bottom, left, right).
- Check if the tooltip overflows the viewport and flip or shift it if so.
- Attach scroll and resize event listeners to update the position continuously.
- Clean up listeners when the tooltip is dismissed.
This is 50–200 lines of JavaScript for a single tooltip. Multiply that by every dropdown, popover, context menu, and autocomplete in a complex application, and you are carrying a significant bundle of positioning logic. Libraries like Floating UI abstract this away, but they add 8–15 KB to your bundle and introduce a runtime cost that scales with the number of positioned elements.
CSS Anchor Positioning replaces all of this with a handful of CSS declarations. The browser does the math in the layout engine, handles scroll tracking internally, and evaluates fallback positions during the same layout pass. No JavaScript, no resize observers, no manual coordinate calculations.
Core Concepts: Anchors and Positioned Elements
The API revolves around two roles:
- Anchor element — The element that acts as the reference point. This is the button a tooltip points to, the trigger a dropdown hangs from, or the icon a popover appears beside. You declare an element as an anchor using the
anchor-nameproperty. - Positioned element — The element that is positioned relative to the anchor. This is the tooltip, dropdown, or popover itself. It must have
position: absoluteorposition: fixed, and it references the anchor using theposition-anchorproperty.
/* Declare the button as an anchor */
.trigger-button {
anchor-name: --my-tooltip-anchor;
}
/* Position the tooltip relative to the anchor */
.tooltip {
position: absolute;
position-anchor: --my-tooltip-anchor;
}
Anchor names are CSS dashed identifiers (they must start with --, similar to custom properties). This scoping prevents collisions and makes the relationship between anchors and positioned elements explicit and traceable in your code.
Positioning with the anchor() Function
Once you have established the anchor relationship, you use the anchor() function in inset properties (top, right, bottom, left) to position the element relative to specific edges of the anchor:
.tooltip {
position: absolute;
position-anchor: --my-tooltip-anchor;
/* Position the tooltip's bottom edge at the anchor's top edge */
bottom: anchor(top);
/* Center horizontally: align tooltip's left to anchor's center,
then shift by half the tooltip width */
left: anchor(center);
translate: -50% 0;
/* Add a small gap */
margin-bottom: 8px;
}
The anchor() function takes a side keyword: top, right, bottom, left, center, start, end, self-start, self-end, and percentage values. The function returns a length value representing the position of that edge of the anchor element, calculated relative to the positioned element's containing block.
You can also reference a specific anchor by name directly in the function if you do not use position-anchor:
.tooltip {
position: absolute;
bottom: anchor(--my-tooltip-anchor top);
left: anchor(--my-tooltip-anchor center);
}
Simplified Positioning with position-area
For common placement patterns (above, below, left, right), writing out individual anchor() calls on multiple inset properties can feel verbose. The position-area property provides a shorthand that specifies placement using a logical grid model:
/* Place the tooltip above the anchor, centered */
.tooltip-top {
position: absolute;
position-anchor: --btn;
position-area: top center;
}
/* Place below the anchor, spanning the full width */
.dropdown {
position: absolute;
position-anchor: --trigger;
position-area: bottom span-all;
}
/* Place to the right of the anchor */
.sidebar-popover {
position: absolute;
position-anchor: --icon;
position-area: center right;
}
The position-area property uses a 3x3 grid mental model centered on the anchor element. The rows are top, center, bottom, and the columns are left, center, right (or their logical equivalents: start, center, end; block-start, center, block-end; inline-start, center, inline-end). You specify two values — one for the block axis and one for the inline axis — to place the positioned element in one of the nine regions around the anchor.
The span-all keyword makes the positioned element stretch across the entire axis, which is useful for dropdowns that should be as wide as the anchor or wider.
Fallback Positions with position-try-fallbacks
One of the most valuable features of CSS anchor positioning is automatic fallback handling. When a positioned element would overflow the viewport or a containing block, the browser can automatically try alternative positions. You define fallbacks using the position-try-fallbacks property combined with @position-try blocks:
.tooltip {
position: absolute;
position-anchor: --btn;
position-area: top center;
/* Try these fallbacks if the primary position overflows */
position-try-fallbacks: --below, --left, --right;
}
@position-try --below {
position-area: bottom center;
}
@position-try --left {
position-area: center left;
}
@position-try --right {
position-area: center right;
}
When the tooltip's primary position (above the button) would cause it to overflow the top of the viewport, the browser automatically evaluates each fallback in order. If --below fits, it uses that. If not, it tries --left, then --right. This is the viewport collision detection that Floating UI handles in JavaScript, now done natively in the CSS layout engine.
Built-in Flip Keywords
For the most common fallback pattern — flipping to the opposite side — you can use the built-in keywords instead of defining custom @position-try blocks:
.tooltip {
position: absolute;
position-anchor: --btn;
position-area: top center;
/* Automatically flip to the bottom if top overflows */
position-try-fallbacks: flip-block;
}
.side-panel {
position: absolute;
position-anchor: --trigger;
position-area: center right;
/* Flip to the left if right overflows */
position-try-fallbacks: flip-inline;
}
.context-menu {
position: absolute;
position-anchor: --target;
position-area: bottom right;
/* Flip in both axes if needed */
position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
}
The flip-block keyword flips the position along the block axis (top becomes bottom and vice versa). The flip-inline keyword flips along the inline axis (left becomes right). You can combine them for diagonal flipping. These keywords eliminate the need for @position-try blocks in simple cases.
Building a Tooltip Step by Step
Let us build a complete, accessible tooltip using anchor positioning. We will start with the HTML, add the CSS, and then handle edge cases:
<button class="btn" popovertarget="tip-1">
Hover for details
</button>
<div id="tip-1" popover class="tooltip" role="tooltip">
This action will save your current progress and create a checkpoint
you can return to later.
</div>
<style>
.btn {
anchor-name: --tip-1-anchor;
}
.tooltip {
position: absolute;
position-anchor: --tip-1-anchor;
position-area: top center;
position-try-fallbacks: flip-block;
margin: 0;
margin-bottom: 8px;
padding: 0.5rem 0.75rem;
max-width: 280px;
background: #1a1a2e;
color: #ffffff;
border-radius: 6px;
font-size: 0.875rem;
line-height: 1.4;
border: none;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
/* Arrow using a pseudo-element */
.tooltip::after {
content: "";
position: absolute;
top: 100%;
left: 50%;
translate: -50% 0;
border: 6px solid transparent;
border-top-color: #1a1a2e;
}
</style>
This example also uses the Popover API (popover attribute and popovertarget) for show/hide behavior. The popover handles visibility, focus management, and light-dismiss behavior, while anchor positioning handles placement. Together, they create a fully functional tooltip with zero JavaScript.
The margin-bottom: 8px creates a gap between the tooltip and the anchor. Unlike JavaScript positioning libraries, which need a dedicated "offset" parameter, CSS anchor positioning uses standard margins for spacing. This is elegant because margins collapse, participate in normal CSS interactions, and are already well-understood by every CSS developer.
Building a Dropdown Menu
Dropdown menus are another natural fit for anchor positioning. Here is a complete dropdown example:
<nav class="nav">
<button class="nav-trigger" popovertarget="dropdown-menu">
Products
</button>
<div id="dropdown-menu" popover class="dropdown">
<ul role="menu">
<li role="menuitem"><a href="/product-a">Product A</a></li>
<li role="menuitem"><a href="/product-b">Product B</a></li>
<li role="menuitem"><a href="/product-c">Product C</a></li>
</ul>
</div>
</nav>
<style>
.nav-trigger {
anchor-name: --dropdown-anchor;
}
.dropdown {
position: absolute;
position-anchor: --dropdown-anchor;
position-area: bottom span-left;
position-try-fallbacks: flip-block;
margin: 0;
margin-top: 4px;
min-width: anchor-size(width);
padding: 0.5rem 0;
background: #ffffff;
border: 1px solid #e0e0e0;
border-radius: 8px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}
.dropdown ul {
list-style: none;
margin: 0;
padding: 0;
}
.dropdown a {
display: block;
padding: 0.5rem 1rem;
color: #333;
text-decoration: none;
}
.dropdown a:hover {
background: #f5f5f5;
}
</style>
Note the use of anchor-size(width) to set min-width. The anchor-size() function returns the dimensions of the anchor element, letting you ensure the dropdown is at least as wide as its trigger button. You can use anchor-size(width), anchor-size(height), anchor-size(block), and anchor-size(inline).
Anchor Positioning and Scroll
One of the key advantages of CSS anchor positioning over JavaScript solutions is scroll handling. When the anchor element scrolls, the positioned element automatically moves with it — the browser tracks the relationship in the layout engine. There are no scroll event listeners to attach, no requestAnimationFrame loops, and no jank from asynchronous position updates.
For positioned elements with position: fixed, the element stays in the viewport while tracking the anchor's position. If the anchor scrolls out of view, the positioned element can be configured to hide automatically using the position-visibility property:
.tooltip {
position: fixed;
position-anchor: --btn;
position-area: top center;
/* Hide when the anchor is not visible */
position-visibility: anchors-visible;
}
The position-visibility: anchors-visible declaration hides the positioned element (equivalent to visibility: hidden) when the anchor scrolls out of the viewport. This prevents orphaned tooltips or popovers from lingering on screen after their trigger has scrolled away.
Multiple Anchors
A single positioned element can reference multiple anchors using the anchor() function directly in inset properties, without relying on position-anchor:
.element-a { anchor-name: --a; }
.element-b { anchor-name: --b; }
/* Stretch a highlight between two anchors */
.highlight {
position: absolute;
top: anchor(--a top);
bottom: anchor(--b bottom);
left: anchor(--a left);
right: anchor(--b right);
background: rgba(0, 102, 204, 0.1);
border: 2px solid #0066cc;
border-radius: 4px;
pointer-events: none;
}
This positions the highlight element to span from element A's top-left corner to element B's bottom-right corner, creating a selection-like highlight between two arbitrary elements. This pattern is useful for annotation tools, code review interfaces, and collaborative editing features.
Progressive Enhancement
As of early 2025, CSS anchor positioning is supported in Chromium-based browsers (Chrome 125+, Edge 125+) but is not yet available in Firefox or Safari, where implementation work is in progress. This means you need a progressive enhancement strategy for production use.
The recommended approach is to use @supports to detect anchor positioning support and provide a JavaScript fallback for non-supporting browsers:
/* Base styles: use JavaScript positioning (Floating UI, etc.) */
.tooltip {
position: absolute;
/* JavaScript will set top/left dynamically */
}
/* Enhanced styles for browsers with anchor positioning */
@supports (anchor-name: --a) {
.tooltip {
position-anchor: --btn;
position-area: top center;
position-try-fallbacks: flip-block;
/* Override any inline styles JavaScript may have set */
inset: auto;
}
}
/* In your JavaScript */
if (!CSS.supports('anchor-name', '--a')) {
// Initialize Floating UI or other positioning library
initFallbackPositioning();
}
This approach lets you ship anchor positioning immediately. Chromium users get the pure CSS experience, while Firefox and Safari users get the JavaScript fallback. As those browsers add support, the JavaScript path will naturally fade out.
Another strategy is to avoid anchor positioning for critical UI and use it only for enhancement. If a tooltip is a nice-to-have rather than essential, you can simply not show it in unsupported browsers, or show it inline rather than positioned.
Performance Characteristics
CSS anchor positioning is implemented in the browser's layout engine, which means it runs on the main thread during the layout phase. This has several implications:
- No JavaScript overhead. There are no event listeners, no DOM reads, no forced reflows. The positioning math happens as part of the normal layout pass.
- Scroll tracking is free. The browser already tracks element positions during scroll; anchor positioning piggybacks on this existing mechanism.
- Fallback evaluation is cheap. The browser evaluates
position-try-fallbacksduring the same layout pass, not in a separate step. This is far more efficient than the measure-try-measure cycle that JavaScript libraries must perform. - Scales with positioned elements. Whether you have 1 or 100 positioned elements, the cost is proportional and handled uniformly by the engine, unlike JavaScript solutions where each positioned element adds its own event listeners and measurement cycles.
Browser Support
CSS Anchor Positioning is a newer API with more limited support than established CSS features:
- Chrome — 125+ (released May 2024)
- Edge — 125+ (same Chromium base)
- Firefox — In development (behind a flag in Nightly builds)
- Safari — In development (implementation work has started in WebKit)
Given the current support landscape, anchor positioning is best used with progressive enhancement. The feature is stable in Chromium, and the specification is at a mature stage, so the API surface is unlikely to change significantly. It is reasonable to start using it in production today with a JavaScript fallback for other browsers.
It is worth noting that the specification has evolved during its development. Earlier drafts used inset-area instead of position-area, and some tutorials and references may still use the older property name. Chrome initially shipped with inset-area and later renamed it to position-area in Chrome 131. If you encounter older code using inset-area, update it to position-area for forward compatibility.
Common Pitfalls
- Forgetting position: absolute or fixed. The positioned element must be absolutely or fixedly positioned. Without this, anchor positioning has no effect.
- Anchor name collisions. Anchor names are global within a document. If two elements share the same anchor name, only the last one in DOM order is used. Use descriptive, unique names.
- Containing block issues. An absolutely positioned element is positioned relative to its nearest positioned ancestor. If the anchor is outside this containing block, coordinates may be unexpected. Using
position: fixedavoids this by positioning relative to the viewport. - Popover and top layer. Elements in the top layer (such as popovers and dialogs) are positioned relative to the viewport, which simplifies anchor positioning since you do not need to worry about containing blocks. Combine anchor positioning with the Popover API for best results.
Further Reading
CSS Anchor Positioning is a powerful addition to the platform that will gradually replace JavaScript positioning libraries for most use cases. To experiment with anchor positioning visually, try our Anchor Position Playground tool, which lets you drag elements around and see the generated CSS in real time.
For more CSS guides and tools, visit the guides index or browse the full Modern CSS Tools collection.