EdgeCases Logo
Feb 2026
CSS
Surface
6 min read

CSS Anchor Positioning: Tooltips Without JavaScript

CSS Anchor Positioning eliminates JavaScript tooltip libraries. One anchor-name, one position-anchor, and the browser handles collision detection.

css
anchor-positioning
tooltips
popovers
modern-css
no-javascript

Positioning tooltips, popovers, and dropdowns has always required JavaScript to calculate coordinates, handle viewport collisions, and update on scroll. CSS Anchor Positioning (Chrome 125+, Edge 125+) eliminates all of that. One anchor-name on the trigger, one position-anchor on the target, and the browser handles the math.

The Basic Pattern

<button style="anchor-name: --tooltip-anchor">Hover me</button>
<div class="tooltip" popover>Tooltip content</div>

<style>
.tooltip {
  position: fixed;
  position-anchor: --tooltip-anchor;
  
  /* Position relative to anchor using the 3x3 grid */
  position-area: top;
  
  /* Offset from anchor edge */
  margin-bottom: 8px;
}
</style>

No JavaScript. No getBoundingClientRect(). No scroll listeners. The browser recalculates position automatically on scroll, resize, and layout changes.

The position-area Grid

position-area (formerly inset-area before Chrome 129) places the positioned element on a conceptual 3×3 grid centered on the anchor:

/* Grid positions:
   top-left    | top    | top-right
   left        | center | right
   bottom-left | bottom | bottom-right
*/

/* Tooltip above anchor, centered */
position-area: top;

/* Dropdown below anchor, aligned left */
position-area: bottom span-right;

/* Popover to the right, vertically centered */
position-area: right;

The span-* modifiers allow spanning multiple grid cells. bottom span-right spans from center-bottom to bottom-right, useful for dropdowns that shouldn't overflow the anchor's left edge.

The anchor() Function for Fine Control

When position-area isn't precise enough, the anchor() function gives pixel-level control:

.tooltip {
  position: fixed;
  position-anchor: --trigger;
  
  /* Position top edge at anchor's bottom edge */
  top: anchor(bottom);
  
  /* Center horizontally with anchor */
  left: anchor(center);
  translate: -50% 0;
  
  /* Or use calc() for offsets */
  top: calc(anchor(bottom) + 8px);
}

anchor() accepts top, bottom, left, right, center, and percentage values. It resolves to a length in the coordinate space of the containing block.

Viewport Collision: position-try-fallbacks

The killer feature. When a tooltip would overflow the viewport, position-try-fallbacks automatically flips to an alternative position:

.tooltip {
  position: fixed;
  position-anchor: --trigger;
  position-area: top;
  
  /* Flip to bottom if top overflows */
  position-try-fallbacks: flip-block;
}

/* Or multiple fallbacks in priority order */
.dropdown {
  position-area: bottom;
  position-try-fallbacks: 
    flip-block,      /* Try top */
    flip-inline,     /* Try left/right */
    flip-block flip-inline;  /* Try opposite corner */
}

Built-in flip options: flip-block (vertical), flip-inline (horizontal), flip-start, flip-both. The browser tries each in order until one fits.

Custom Fallbacks with @position-try

For complex fallback behavior—different sizing, different offsets—define custom position-try rules:

@position-try --compact-bottom {
  position-area: bottom;
  width: 150px;  /* Narrower to fit */
  max-height: 200px;
}

@position-try --side-panel {
  position-area: right;
  width: 250px;
}

.tooltip {
  position-area: top;
  width: 300px;
  
  position-try-fallbacks: 
    flip-block,
    --compact-bottom,
    --side-panel;
}

Each @position-try block can override any positioning properties. The browser evaluates overflow for each option in sequence.

Dynamic Anchors and Multiple Tooltips

For dynamically generated content, inline styles are the cleanest approach since each anchor needs a unique name:

// JavaScript generates inline anchor-name
items.forEach((item, i) => {
  button.style.anchorName = `--item-${i}`;
  tooltip.style.positionAnchor = `--item-${i}`;
});

Alternatively, use anchor-name: auto combined with ID references (still experimental):

<button id="btn-1">...</button>
<div class="tooltip" style="position-anchor: --btn-1">...</div>

Integration with Popover API

Anchor positioning pairs naturally with the Popover API. Use popovertarget to create the implicit association, then add anchor positioning for layout:

<button popovertarget="menu" style="anchor-name: --menu-trigger">
  Open Menu
</button>

<div id="menu" popover style="position-anchor: --menu-trigger">
  <!-- Menu content -->
</div>

<style>
[popover] {
  position: fixed;
  margin: 0;
  position-area: bottom span-right;
  position-try-fallbacks: flip-block;
}
</style>

The Popover API handles show/hide, focus management, and light-dismiss. Anchor positioning handles layout. Zero JavaScript for a fully functional dropdown.

Edge Cases and Gotchas

Anchor Must Be in DOM Before Target

The anchor element must exist in the DOM when the positioned element renders. If you're dynamically inserting both, ensure the anchor is added first or use requestAnimationFrame to defer positioning.

position: fixed Required

Anchor-positioned elements must use position: fixed or position: absolute. The anchor() function won't work with static or relative positioning.

Containing Block Matters

With position: absolute, the anchor must share a containing block with the positioned element. For tooltips in deeply nested components, position: fixed is safer—it always positions relative to the viewport.

Property Rename: inset-area → position-area

Chrome 125-128 used inset-area. Chrome 129+ uses position-area. Similarly, position-try-options became position-try-fallbacks. Support both during the transition:

.tooltip {
  inset-area: top;        /* Chrome 125-128 */
  position-area: top;     /* Chrome 129+ */
}

Browser Support and Fallbacks

As of early 2026: Chrome/Edge 125+, no Firefox or Safari support yet. For progressive enhancement:

@supports (anchor-name: --test) {
  .tooltip {
    position: fixed;
    position-anchor: --trigger;
    position-area: top;
  }
}

@supports not (anchor-name: --test) {
  /* Fallback: absolute positioning with JS */
  .tooltip {
    position: absolute;
  }
}

The CSS Anchor Positioning Polyfill provides partial support for other browsers, though with performance tradeoffs on scroll-heavy pages.

Key Takeaways

  • anchor-name on trigger + position-anchor on target creates the relationship
  • position-area uses a 3×3 grid for common placements; anchor() for pixel precision
  • position-try-fallbacks handles viewport collision automatically—no JS resize handlers
  • Pairs seamlessly with Popover API for zero-JS dropdowns and tooltips
  • Currently Chromium-only; use feature detection and polyfills for broader support

Advertisement

Related Insights

Explore related edge cases and patterns

CSS
Deep
CSS Scroll-Driven Animations: When animation-timeline Breaks Your Layout
7 min
CSS
Surface
CSS Inset: The Modern Shorthand for Positioning
5 min

Advertisement