# CSS Anchor Positioning for Popovers and Tooltips

Source: https://www.egnworks.com/blog/css-anchor-positioning  
Author: Jacob Val  
Published: 2026-09-11  
Updated: 2026-09-12  
Category: Frontend Architecture  
Tags: CSS Anchor Positioning, CSS

> A deep technical guide to the CSS Anchor Positioning API, covering anchor-name, position-anchor, the anchor() and anchor-size() functions, position-area, custom fallback positions with @position-try, position-visibility, browser support, and a complete dropdown example.

---

Positioning a tooltip or dropdown next to the element that triggered it has never been a native CSS problem. A dropdown has to sit below its trigger unless the viewport runs out of room, in which case it has to flip above it. A tooltip has to stay attached to a button that might move if the page scrolls or the layout shifts. Solving this in CSS alone was not possible, so the job fell to JavaScript libraries that read element positions on every scroll and resize event and recalculated coordinates by hand. The CSS Anchor Positioning module replaces that runtime calculation with a set of properties and functions that the layout engine evaluates directly.

## Why This Was Always a JavaScript Problem

CSS positioning ties an element to its containing block, not to an arbitrary element elsewhere in the DOM. A tooltip absolutely positioned inside a card can be placed relative to that card, but it cannot be told to sit below a specific button three levels up the tree without JavaScript computing that button's coordinates and writing them into inline styles or CSS custom properties.

Libraries such as Popper.js and its successor Floating UI exist specifically to solve this. They attach scroll and resize listeners, measure both elements on every relevant event, run collision detection against the viewport, and write the result back as inline positioning styles. It works, but it adds a runtime dependency, a bundle size cost, and a layer of JavaScript that has to run correctly for every menu, tooltip, and popover on the page.

## The Anchor and Its Target

Anchor positioning introduces two roles. An element becomes an anchor by declaring `anchor-name`, a custom identifier written as a dashed ident.

```css
.trigger {
  anchor-name: --tooltip-trigger;
}
```

A separate element, which must be absolutely or fixed positioned, binds to that anchor with `position-anchor`.

```css
.tooltip {
  position: fixed;
  position-anchor: --tooltip-trigger;
}
```

Once bound, the tooltip has access to the trigger's geometry through CSS functions, regardless of where either element sits in the DOM tree or whether they share a positioned ancestor. That last part is what ordinary absolute positioning cannot do: an anchor and its positioned element can live in completely unrelated branches of the document.

## Reading Anchor Edges with anchor()

The `anchor()` function returns a length by reading a named edge or point of the anchor, and is used inside inset properties such as `top`, `left`, `bottom`, and `right`.

```css
.tooltip {
  position: fixed;
  position-anchor: --tooltip-trigger;
  top: anchor(bottom);
  left: anchor(center);
  translate: -50% 8px;
}
```

The keyword passed to `anchor()` can be a physical side (`top`, `bottom`, `left`, `right`), a logical side (`start`, `end`, `self-start`, `self-end`), a special keyword (`center`, `inside`, `outside`), or a percentage across the anchor's box. An optional second argument supplies a fallback length to use if the referenced anchor does not exist, and the anchor name itself can be passed explicitly as the first argument when an element needs to read from an anchor other than the one set by `position-anchor`. The `translate` in the example above shifts the tooltip to account for its own width and adds a small gap, since `anchor()` reports the anchor's geometry, not the positioned element's own size.

## The Grid Based Alternative: position-area

Reaching for `anchor()` on every inset property is precise but verbose for the common case of placing an element on one side of its anchor. `position-area` covers that case directly by treating the anchor as the center cell of an implicit three by three grid and placing the positioned element on one of the surrounding tiles.

```css
.tooltip {
  position: fixed;
  position-anchor: --tooltip-trigger;
  position-area: top center;
}
```

`top center` places the tooltip directly above the trigger, horizontally centered. Physical keywords (`top`, `bottom`, `left`, `right`, `center`), logical keywords (`block-start`, `block-end`, `inline-start`, `inline-end`), and coordinate keywords (`x-start`, `x-end`, `y-start`, `y-end`) are all valid, and a tile can be widened with `span-all` or a directional span keyword when the positioned element should straddle more than one cell. For a straightforward side placement, `position-area` reads more clearly than the equivalent pair of `anchor()` calls, and the two approaches can be mixed on the same element when one axis needs grid placement and the other needs a precise offset.

## Sizing an Element to Match Its Anchor

`anchor-size()` mirrors `anchor()` for dimensions instead of edges, returning a length based on the anchor's width, height, or the anchor's own containing block.

```css
.dropdown-panel {
  position: fixed;
  position-anchor: --dropdown-trigger;
  position-area: bottom center;
  width: anchor-size(width);
}
```

This keeps a dropdown panel exactly as wide as the control that opened it, without JavaScript reading the trigger's bounding box on open and writing an inline width. `anchor-size()` accepts the same optional anchor name and fallback length arguments as `anchor()`, and its result can be used inside `calc()` the same way, for example doubling the anchor's width with `calc(anchor-size(width) * 2)`.

## Automatic and Custom Fallback Positions

A tooltip anchored to a button near the bottom of the viewport has nowhere to go if it always renders below that button. `position-try-fallbacks` gives the browser a list of alternative positions to attempt, in order, whenever the preferred placement would overflow.

```css
.tooltip {
  position: fixed;
  position-anchor: --tooltip-trigger;
  top: anchor(bottom);
  position-try-fallbacks: flip-block;
}
```

`flip-block` and `flip-inline` cover the common case of swapping to the opposite side of the anchor on one axis. When the built-in flip keywords are not specific enough, `@position-try` defines a named, reusable fallback with its own full set of positioning, sizing, and alignment descriptors.

```css
@position-try --tooltip-left {
  position-area: left center;
  margin-right: 8px;
}

.tooltip {
  position: fixed;
  position-anchor: --tooltip-trigger;
  position-area: top center;
  position-try-fallbacks: --tooltip-left, flip-block;
}
```

The browser tries `--tooltip-left` first, then falls back to `flip-block`, then keeps the original placement if both would still overflow. Values declared inside `@position-try` take precedence over the base declarations on the element while that fallback is active.

## Hiding an Element When Its Anchor Is Not Usable

Repositioning is not always the right response to an anchor that has scrolled away or disappeared. `position-visibility` can hide the positioned element entirely, treating it as strongly hidden, equivalent to `visibility: hidden` on the element and everything inside it.

```css
.tooltip {
  position: fixed;
  position-anchor: --tooltip-trigger;
  position-visibility: no-overflow;
}
```

`anchors-visible`, the default, hides the element once its anchor is fully covered or scrolled out of view. `no-overflow` is stricter, hiding the element as soon as it would itself overflow its containing block or the viewport, even if the anchor is still visible. `anchors-valid` hides the element if `position-anchor` no longer points at a real element at all. The specification recommends reaching for `position-try-fallbacks` first, since keeping the element visible in a different spot is usually better for the user than hiding it outright, and treating `position-visibility` as the option for cases where no repositioning makes sense.

## Scoping Anchor Names

`anchor-name` is a plain identifier, which becomes a problem the moment a component with an anchor is rendered more than once on the same page, since every instance would declare the same name. `anchor-scope` limits which elements a given `anchor-name` is visible to, so a component repeated in a list can bind each of its own tooltips to its own trigger without the names colliding across instances.

## A Complete Dropdown Example

Combining the pieces above produces a dropdown that matches its trigger's width, prefers to open downward, and flips upward when the viewport runs out of room below.

```css
.menu-trigger {
  anchor-name: --menu-trigger;
}

@position-try --menu-above {
  position-area: top center;
}

.menu-panel {
  position: fixed;
  position-anchor: --menu-trigger;
  position-area: bottom center;
  width: anchor-size(width);
  margin-top: 4px;
  position-try-fallbacks: --menu-above;
  position-visibility: anchors-visible;
}
```

Every part of this, including the automatic reflow on scroll, resize, and content changes, runs inside the browser's layout engine. No script measures either element, and nothing needs to re-run when the page changes shape.

## Browser Support and a Path for Older Browsers

CSS anchor positioning has reached Baseline availability, with full support in Chromium based browsers and Safari, and partial support currently rolling out in Firefox. For a codebase that still needs to support a browser without native support, the OddBird polyfill implements the specification in JavaScript, applied only as a fallback rather than as the primary mechanism, so the CSS itself stays the same regardless of which browser renders it.

## What It Does Not Replace

Anchor positioning solves placement, not disclosure. Deciding whether a popover is open, trapping focus inside it, and closing it on an outside click or an escape key press are still separate concerns, typically handled by the native Popover API or a small amount of application logic. Anchor positioning pairs naturally with the Popover API, since a popover element can be anchored to the control that opened it without either feature depending on the other.

It also does not remove every reason to reach for a JavaScript positioning library. Complex cases such as anchoring to a point that changes anchor entirely based on application state, or coordinating multiple anchored elements that must avoid overlapping each other, are still easier to express in JavaScript today.

## When to Reach for It

| Situation | Recommendation |
| --- | --- |
| A tooltip, dropdown, or simple popover anchored to one trigger element | Use native anchor positioning, since the browser already recalculates position on scroll and resize |
| A project that must support a browser without native support today | Add the OddBird polyfill rather than reaching for a full positioning library |
| A floating element with complex, state driven anchor logic | A JavaScript library such as Floating UI may still be simpler to reason about |

## Conclusion

CSS anchor positioning takes a problem that used to require a runtime library, tracking one element's position and size relative to another as the page scrolls and resizes, and moves it into the layout engine itself. Between `anchor()` for precise offsets, `position-area` for common side placements, `anchor-size()` for matching dimensions, and `@position-try` for custom fallback behavior, the module covers the same ground a positioning library covers today, without a scroll listener or a bundle dependency. With support now broad across major browsers and a polyfill available for the rest, it is a reasonable default for new popovers and tooltips rather than an experimental feature to wait on.

## References

[MDN: CSS Anchor Positioning Module](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_anchor_positioning)

[MDN: anchor() Function](https://developer.mozilla.org/en-US/docs/Web/CSS/anchor)

[MDN: position-area](https://developer.mozilla.org/en-US/docs/Web/CSS/position-area)

[MDN: anchor-size() Function](https://developer.mozilla.org/en-US/docs/Web/CSS/anchor-size)

[MDN: @position-try](https://developer.mozilla.org/en-US/docs/Web/CSS/@position-try)

[MDN: position-visibility](https://developer.mozilla.org/en-US/docs/Web/CSS/position-visibility)

[OddBird: CSS Anchor Positioning Polyfill](https://github.com/oddbird/css-anchor-positioning)

[Can I Use: CSS Anchor Positioning](https://caniuse.com/css-anchor-positioning)
