CSS Anchor Positioning: Tooltips and Overlays Without JavaScript
AI generated
CSS · Anchor Positioning · Tooltips · Modern CSS
CSS Anchor Positioning
Tooltips and Overlays Without JavaScript

Tooltips, dropdowns and contextual overlays were solved for years either with JavaScript or with fragile CSS hacks. CSS Anchor Positioning changes that fundamentally: anchor(), position-anchor and @position-try enable precise, context-aware positioning directly in the stylesheet, without a single line of JavaScript.

12 min read anchor() · position-anchor · @position-try · inset-area Chrome 125+ · Firefox 131+ · Safari 18+

1. The Problem With Tooltip Positioning Before Anchor Positioning

Positioning tooltips, dropdowns and contextual overlays has been one of the most unsatisfying tasks in frontend development for years. The core problem: an overlay needs to be positioned relative to its trigger, an element that sits somewhere in the DOM, while the overlay itself often lives in a different place in the document tree, for example right before the closing <body> tag, to avoid stacking context problems. CSS Anchor Positioning addresses exactly this conflict between DOM position and visual positioning.

Earlier solutions relied either on JavaScript libraries such as Popper.js or Floating UI, which position the overlay dynamically via getBoundingClientRect(), or on CSS hacks using position: relative on the parent element, which ties the overlay to the trigger in the DOM and creates stacking problems. Both approaches have drawbacks: JavaScript solutions add extra bytes and do not always react synchronously to layout changes. CSS hacks break down in complex layouts with nested stacking contexts. CSS Anchor Positioning solves both natively in the browser.

2. Basics: anchor-name and position-anchor

The heart of CSS Anchor Positioning is two new CSS properties. The anchor-name property marks any element as an anchor and assigns it a custom-property-like identifier, for example anchor-name: --my-button. The overlay that should align to this anchor references that name via position-anchor: --my-button. This logically links the two elements, regardless of where they sit in the DOM tree. The connection exists purely in CSS, with no JavaScript and no DOM mutation.

The overlay must have position: absolute or position: fixed set for CSS Anchor Positioning to take effect. With position: fixed, the overlay stays correctly positioned relative to the anchor while scrolling, because the browser updates the offset automatically. That is a significant advantage over JavaScript solutions, which have to listen for scroll events. The anchor positioning system runs in the same layout pass as every other CSS property, so there is no asynchronous repositioning and no layout thrashing.


/* Step 1: Mark the trigger element as an anchor */
.tooltip-trigger {
  anchor-name: --tooltip-anchor;
  /* Works on any element: button, span, div, img */
}

/* Step 2: Link the overlay to the anchor */
.tooltip {
  position: fixed; /* or absolute, fixed survives scrolling */
  position-anchor: --tooltip-anchor;

  /* Step 3: Position relative to anchor edges */
  bottom: anchor(top);        /* overlay bottom = anchor top */
  left: anchor(center);       /* overlay left  = anchor horizontal center */
  translate: -50% -0.5rem;    /* center and add gap */

  /* Visibility: linked to :popover-open or :hover chain */
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease;
}

.tooltip-trigger:hover .tooltip,
.tooltip-trigger:focus .tooltip {
  opacity: 1;
  pointer-events: auto;
}

3. The anchor() Function: Precise Edge References

The anchor() function is the tool used to read concrete coordinates from the anchor element. It can be used in the overlay's inset properties: top, right, bottom, left, inset-block-start, inset-inline-end and similar. As an argument, anchor() takes an edge keyword: top, bottom, left, right, center, start, end or a percentage value. An optional second argument is a fallback value in case the anchor does not exist or cannot be resolved.

The grammar is intentionally logical: bottom: anchor(top) means the bottom edge of the overlay aligns with the top edge of the anchor. left: anchor(right) places the overlay to the right of the anchor. Combined with margin or translate, fine-grained spacing becomes easy to achieve. Particularly elegant is anchor(center), which returns the anchor's centerline, horizontal or vertical depending on context. CSS Anchor Positioning also knows the anchor-size() function, which returns the width or height of the anchor and allows the overlay to be set to exactly the same width as its trigger.

4. @position-try: Automatic Fallback Positioning

One of the most powerful features of CSS Anchor Positioning is the @position-try at-rule. It solves the classic viewport overflow problem: if a tooltip does not have enough room above, it should appear below instead. If a dropdown would extend past the right edge of the viewport, it should align to the left instead. Until now, this logic required JavaScript to check viewport boundaries and swap CSS classes.

With @position-try --fallback-name { … } you define alternative positioning rules. The position-try-fallbacks property on the overlay takes an ordered list of these fallbacks. The browser tries the options in order and picks the first one where the overlay fits entirely within the viewport, automatically, with no JavaScript, on every layout reflow. CSS Anchor Positioning combines this with the position-try-order property, which controls whether the browser prefers the option with the most available space instead of strictly working through the list in order.


/* Define fallback positioning strategies */
@position-try --above {
  bottom: anchor(top);
  left: anchor(center);
  translate: -50% -0.5rem;
}

@position-try --below {
  top: anchor(bottom);
  left: anchor(center);
  translate: -50% 0.5rem;
}

@position-try --right {
  top: anchor(center);
  left: anchor(right);
  translate: 0.5rem -50%;
}

@position-try --left {
  top: anchor(center);
  right: anchor(left);
  translate: -0.5rem -50%;
}

/* Apply: browser picks first fitting option */
.tooltip {
  position: fixed;
  position-anchor: --tooltip-anchor;

  /* Primary position */
  bottom: anchor(top);
  left: anchor(center);
  translate: -50% -0.5rem;

  /* Automatic overflow handling */
  position-try-fallbacks: --above, --below, --right, --left;
  position-try-order: most-space; /* prefer option with most available space */
}

5. inset-area: Grid-Based Positioning

Besides the anchor() function, inset-area offers a second, conceptually different way to position elements in CSS Anchor Positioning. The property thinks in terms of an imaginary 3x3 grid around the anchor element: top left, top center, top right, center left, center, center right, bottom left, bottom center, bottom right. These are referred to as regions. With inset-area: top the overlay is placed in the top center, with inset-area: end start in the logical bottom left corner.

The advantage of inset-area over the anchor() function lies in readability: you describe the desired region rather than concrete edge values. The overlay adapts automatically to the anchor size. CSS Anchor Positioning also allows two-value inset-area declarations such as inset-area: bottom span-right, which covers the entire bottom row from the anchor's midpoint to the right, ideal for context-menu-style overlays. Combining inset-area with position-try-fallbacks makes it trivial to write overlays that automatically adapt to the available viewport space.

6. Practical Example: A Tooltip Component Entirely in CSS

A complete tooltip component built with CSS Anchor Positioning needs not a single line of JavaScript. The browser's Popover API handles visibility control: the tooltip element gets the popover attribute, the trigger button gets popovertarget. The browser links the two automatically and takes care of accessibility attributes such as aria-expanded and focus management. CSS is left to handle only positioning and animation.

The interplay of the Popover API and CSS Anchor Positioning is particularly strong: because the popover element renders in the browser's top layer, above all normal stacking contexts, the classic z-index problems disappear entirely. At the same time, CSS Anchor Positioning still allows the overlay to be placed precisely relative to its trigger, even if the trigger sits deep inside a nested stacking context. The combination of the two APIs is the real breakthrough for JavaScript-free overlays.


/* Complete CSS-only tooltip using Popover API + Anchor Positioning */

/* Anchor: the trigger button */
[popovertarget="my-tooltip"] {
  anchor-name: --tooltip-btn;
}

/* The tooltip popover */
#my-tooltip {
  /* Popover resets to display:none by default */
  position: fixed;
  position-anchor: --tooltip-btn;
  inset: auto; /* reset default popover insets */

  /* Position above the anchor */
  bottom: calc(anchor(top) + 0.5rem);
  left: anchor(center);
  translate: -50% 0;

  /* Size and style */
  max-width: 20rem;
  padding: 0.5rem 0.75rem;
  border-radius: 0.5rem;
  background: #1e1b4b;
  color: #ede9fe;
  font-size: 0.875rem;
  border: none;
  box-shadow: 0 4px 24px rgba(74,29,150,0.35);

  /* Automatic flip if not enough space above */
  position-try-fallbacks: flip-block;

  /* Smooth entry animation (uses @starting-style) */
  transition: opacity 0.15s ease, scale 0.15s ease, display 0.15s ease allow-discrete;
  opacity: 1;
  scale: 1;
}

/* Before opening (entry animation start state) */
@starting-style {
  #my-tooltip:popover-open {
    opacity: 0;
    scale: 0.95;
  }
}

/* Hidden state */
#my-tooltip:not(:popover-open) {
  opacity: 0;
  scale: 0.95;
}

Dropdown menus are another central use case for CSS Anchor Positioning. The classic problem: a navigation dropdown must begin below its trigger and align with its left edge, no matter how wide the menu is. With CSS Anchor Positioning you write top: anchor(bottom) and left: anchor(left). If the menu would exceed the right edge of the viewport, a @position-try fallback automatically kicks in, aligning the right edge of the menu with the right edge of the trigger.

A common dropdown pattern is width matching: the dropdown should be at least as wide as its trigger. With min-width: anchor-size(width) this is done in a single line of CSS. Besides anchor-size(width), CSS Anchor Positioning also knows anchor-size(height), anchor-size(inline) and anchor-size(block) for writing-direction-independent sizing. This replaces entire JavaScript functions that previously read the trigger width via getBoundingClientRect() and applied it to the dropdown via style.minWidth.

8. Browser Support and Progressive Enhancement

Browser support for CSS Anchor Positioning has become pleasingly broad since 2025, though it is not yet universal. Chrome and Edge support the full specification starting with version 125. Firefox delivered support starting with version 131. Safari implemented the core functionality starting with version 18, though some advanced features such as position-try-order: most-space are still missing. For production deployments, feature detection with @supports (anchor-name: --x) is recommended.

Progressive enhancement works well with CSS Anchor Positioning: the fallback for unsupporting browsers is classic positioning with position: absolute relative to the parent element. The overlay may not work perfectly at every viewport size in that case, but it remains visible and usable. You then add the anchor positioning rules inside a @supports block. That way modern browsers benefit from precise positioning while older browsers get a functional fallback, with no JavaScript required in either case.

9. Anchor Positioning in Direct Comparison

Comparing classic tooltip techniques with CSS Anchor Positioning shows the advantages across several dimensions: bundle size, accessibility, maintainability and correctness in complex layouts.

Criterion Popper.js / Floating UI CSS Anchor Positioning Advantage
Bundle size ~12 KB (Floating UI core) 0 KB (browser native) No third-party package
Viewport flip JS logic, scroll events @position-try, automatic No event listener
Stacking context Z-index management needed Top layer via Popover API No z-index conflicts
Accessibility Manual ARIA maintenance Popover API, browser managed aria-expanded, focus automatic
Overlay width getBoundingClientRect() + JS anchor-size(width) One line of CSS

The table shows that CSS Anchor Positioning is not just a technical gimmick but a serious replacement for JavaScript-based positioning libraries in modern projects. The one real remaining drawback is browser support that is not yet fully complete, which calls for progressive enhancement. Once all relevant browsers have fully implemented the specification, there will be no remaining case for Popper.js in new projects.

Mironsoft

Modern CSS, frontend architecture and performance optimization

JavaScript-free UI components for your project?

We bring modern CSS APIs such as Anchor Positioning, the Popover API and View Transitions into your codebase, for leaner bundles, better accessibility and fewer JavaScript dependencies.

CSS audit

Analyzing and modernizing existing tooltip and overlay implementations

Component development

Tooltips, dropdowns and popovers built as progressively enhanced CSS components

Bundle optimization

Replacing Popper.js and similar libraries with native CSS solutions

10. Summary

CSS Anchor Positioning solves a fundamental problem in web development: positioning overlays relative to arbitrary elements in the DOM, independent of their position in the document tree. The three core concepts, anchor-name and position-anchor for linking, the anchor() function for precise edge references and @position-try for automatic overflow handling, fully replace JavaScript libraries such as Popper.js for most use cases. Combined with the browser's Popover API, z-index problems and manual accessibility implementations also disappear.

Browser support reached a level of maturity in 2025 that justifies using CSS Anchor Positioning in new projects with progressive enhancement. For projects that still need to support older browsers, wrapping usage in a @supports block is the right approach. The long-term direction is clear: overlay positioning belongs in CSS, not in JavaScript. Teams working with modern CSS APIs today build leaner, more maintainable and more accessible interfaces.

CSS Anchor Positioning: The Essentials at a Glance

Linking

anchor-name: --name on the trigger, position-anchor: --name on the overlay. DOM-independent linking purely in CSS.

Positioning

anchor(top/bottom/left/right/center) in inset properties. anchor-size(width) for width matching.

Overflow handling

@position-try defines alternatives. position-try-fallbacks sets the order. The browser chooses automatically.

Browser support

Chrome 125+, Firefox 131+, Safari 18+. Progressive enhancement via @supports (anchor-name: --x).

11. FAQ: CSS Anchor Positioning

1What is CSS Anchor Positioning?
A native CSS API for DOM-independent positioning of overlays relative to any anchor element, without JavaScript.
2Do I need JavaScript for tooltips?
No. With Anchor Positioning plus the Popover API you can build fully JS-free tooltips. Accessibility is managed automatically by the browser.
3anchor() vs. inset-area?
anchor() returns concrete edge values. inset-area thinks in a 3x3 grid and is more readable for common patterns (top, bottom, left, right).
4What does @position-try do?
Defines alternative positioning rules. The browser automatically picks the first variant where the overlay fits entirely within the viewport, no JavaScript needed.
5Browser support?
Chrome/Edge 125+, Firefox 131+, Safari 18+. Progressive enhancement via @supports (anchor-name: --x) for older browsers.
6Can Anchor Positioning replace Popper.js?
For tooltips, dropdowns and context menus, yes. No bundle overhead, automatic viewport flipping, width matching with anchor-size().
7Avoiding z-index problems?
Use the Popover API: popovers render in the browser's top layer, above all stacking contexts. Z-index conflicts are structurally ruled out.
8What is anchor-size()?
Returns the width or height of the anchor. min-width: anchor-size(width) makes the overlay exactly as wide as its trigger, in one line of CSS.
9Does Anchor Positioning work with position: fixed?
Yes, and it is even recommended. position: fixed keeps the overlay anchored automatically while scrolling. The browser updates the offset in the same layout pass.
10How to implement progressive enhancement?
@supports (anchor-name: --x) { … }. Fallback: classic position: absolute relative to the parent element. Add the anchor positioning rules inside the @supports block.