Skip to main content

CSS Anchor Positioning: No More JavaScript for Tooltips

April 28, 2026

</>

For twenty years, positioning a tooltip next to a button required JavaScript. You calculated bounding rects, handled scroll offsets, flipped the tooltip when it hit the viewport edge, and re-ran the whole calculation on resize. Libraries like Popper.js and Floating UI exist entirely to solve this problem.

CSS anchor positioning is the browser-native solution. It is fully supported in Chrome and Edge, behind a flag in Firefox, and shipping in Safari. In 2026 it is ready to use with a fallback strategy.

The core concept

Anchor positioning links a positioned element (the "floating" element) to another element (the "anchor") so the floating element can be placed relative to it — and automatically repositioned when it would overflow the viewport.

Two new CSS features make this work: anchor-name on the anchor element, and position-anchor plus anchor() functions on the floating element.

Basic tooltip

<button class="trigger">Hover me</button>
<div class="tooltip" popover>This is a tooltip</div>
.trigger {
  anchor-name: --trigger;
}

.tooltip {
  position: absolute;
  position-anchor: --trigger;

  bottom: calc(anchor(top) + 8px);
  left: anchor(center);
  translate: -50% 0;
}

The anchor() function references a physical edge (top, bottom, left, right) or logical position (center, start, end) of the named anchor. The floating element positions itself relative to those edges.

No JavaScript. No getBoundingClientRect. No resize observer.

Automatic overflow handling with position-try-fallbacks

The real power is position-try-fallbacks. You define multiple placement strategies and the browser picks the first one that keeps the element in the viewport.

.tooltip {
  position: absolute;
  position-anchor: --trigger;

  /* Primary placement: above */
  bottom: calc(anchor(top) + 8px);
  left: anchor(center);
  translate: -50% 0;

  /* Fallbacks: flip below, then to the right */
  position-try-fallbacks:
    --flip-below,
    --flip-right;
}

@position-try --flip-below {
  top: calc(anchor(bottom) + 8px);
  bottom: unset;
}

@position-try --flip-right {
  left: calc(anchor(right) + 8px);
  bottom: unset;
  top: anchor(center);
  translate: 0 -50%;
}

Define the fallback placements with @position-try, list them in position-try-fallbacks, and the browser handles the rest. This is exactly what Popper.js does — minus the JavaScript and the runtime calculation overhead.

Combining with the Popover API

Anchor positioning pairs naturally with the HTML Popover API. Popovers handle show/hide, accessibility, and light-dismiss behaviour natively; anchor positioning handles placement.

<button popovertarget="menu" id="menu-trigger">Open menu</button>

<ul popover id="menu">
  <li>Option 1</li>
  <li>Option 2</li>
  <li>Option 3</li>
</ul>
#menu-trigger {
  anchor-name: --menu-trigger;
}

#menu {
  position: absolute;
  position-anchor: --menu-trigger;

  top: calc(anchor(bottom) + 4px);
  left: anchor(left);

  position-try-fallbacks: --flip-above;
}

@position-try --flip-above {
  top: unset;
  bottom: calc(anchor(top) + 4px);
}

No JavaScript at all. The button toggles the popover, the popover positions itself relative to the button, and it flips when it hits the viewport edge. This is a fully functional dropdown menu.

Inset-area: the shorthand approach

For common placements, inset-area provides a grid-based shorthand. Think of the anchor as the centre of a 3x3 grid — you pick which cell the floating element occupies.

.tooltip {
  position: absolute;
  position-anchor: --trigger;
  inset-area: top center;   /* above and centred */
}

Other values: bottom, left, right, top left, bottom right, span-all (centres across multiple cells). For simple placements this is much cleaner than manual anchor() calculations.

Browser support and fallback strategy

Full support: Chrome 125+, Edge 125+ In progress: Firefox (behind flag), Safari (in development)

For 2026 production use, the strategy is progressive enhancement:

.tooltip {
  /* Fallback: static positioning for unsupported browsers */
  position: absolute;
  top: 100%;
  left: 0;
}

@supports (anchor-name: --x) {
  .tooltip {
    position-anchor: --trigger;
    inset-area: bottom span-all;
    position-try-fallbacks: --flip-top;
  }
}

Users on unsupported browsers get a basic static tooltip. Users on modern browsers get smart viewport-aware positioning. The JavaScript layer can be removed entirely for the supported cohort.

When to still use Floating UI

Anchor positioning covers the common cases well. There are scenarios where JavaScript libraries still add value:

  • Virtual elements — anchoring to a cursor position or a point in a canvas where there is no real DOM element
  • Complex sub-pixel calculations — some designs need positioning logic beyond what anchor() provides
  • Legacy browser requirements — if you need to support browsers without anchor positioning and do not want the fallback complexity

For most tooltip, dropdown, popover, and context menu use cases in 2026, the native approach is cleaner and cheaper. Start there.

The broader shift

CSS anchor positioning is part of a pattern where browser capabilities are absorbing what used to require JavaScript libraries: scroll snap, view transitions, the Popover API, container queries, has() selector. The JavaScript overhead for UI interactions is shrinking.

This is not a reason to stop learning JavaScript. It is a reason to check what the platform can do before reaching for a library.

Recommended Posts