Skip to main content

CSS Anchor Positioning Guide

Position tooltips, popovers, and dropdowns relative to anchor elements
using pure CSS - no JavaScript positioning libraries needed.

Experimental Feature

CSS Anchor Positioning is an experimental feature currently supported in Chromium-based browsers (Chrome 125+, Edge 125+). Safari and Firefox do not yet support this feature. Always provide fallback positioning for production use.

1. Introduction to CSS Anchor Positioning

CSS Anchor Positioning is a new CSS feature that allows you to position elements relative to other "anchor" elements on the page. This is incredibly useful for creating tooltips, popovers, dropdown menus, and other UI components that need to be positioned relative to a trigger element.

Common Use Cases

  • Tooltips - Display helpful text near a button or link
  • Popovers - Show additional content or forms
  • Dropdown menus - Position menus below navigation items
  • Context menus - Right-click menus positioned at cursor
  • Floating labels - Labels that follow form inputs
  • Tour guides - Highlight and explain UI elements

Browser Support

Chrome
125+
Edge
125+
Safari
No
Firefox
No
Opera
111+

Note: Since this is an experimental feature, you should always provide fallback positioning using JavaScript libraries like Floating UI or Popper.js for production applications that need cross-browser support.

Before Anchor Positioning

Previously, positioning tooltips and popovers required JavaScript to calculate positions, handle viewport boundaries, and update positions on scroll. Now, CSS can handle all of this natively.

Old Approach (JavaScript)

  • Calculate element positions manually
  • Handle scroll and resize events
  • Manage viewport boundary detection
  • Use libraries like Popper.js or Floating UI

New Approach (CSS)

  • Declare anchor with anchor-name
  • Position with anchor() function
  • Browser handles viewport automatically
  • No JavaScript needed for positioning

2. Creating an Anchor

To use anchor positioning, you first need to designate an element as an anchor using theanchor-name property.

The anchor-name Property

/* Define an element as an anchor */
.trigger-button {
  anchor-name: --my-anchor;
}

/* The name must start with two dashes (--) like CSS custom properties */

Naming Conventions

  • Anchor names must start with -- (two dashes)
  • Use descriptive names: --tooltip-trigger, --dropdown-btn
  • Each anchor name should be unique on the page
  • Names follow the same rules as CSS custom properties

Basic Anchor Example

<!-- HTML -->
<button class="trigger-button">Hover me</button>
<div class="tooltip">I'm a tooltip!</div>
/* CSS */
.trigger-button {
  anchor-name: --tooltip-anchor;
}

.tooltip {
  /* We'll position this relative to the anchor */
  position: fixed;
  position-anchor: --tooltip-anchor;
}

Important: The anchored element (tooltip, popover, etc.) should useposition: fixed orposition: absolute for anchor positioning to work correctly.

3. Positioning with Anchors

Once you have an anchor, you can position other elements relative to it using theposition-anchor property and theanchor() function.

The position-anchor Property

/* Connect an element to an anchor */
.tooltip {
  position: fixed;
  position-anchor: --tooltip-anchor;
}

The anchor() Function

The anchor() function returns the position of the anchor element's edge. Use it in inset properties (top, right, bottom, left) to position your element.

/* Position tooltip below the anchor */
.tooltip {
  position: fixed;
  position-anchor: --tooltip-anchor;

  /* Position the tooltip's top edge at the anchor's bottom edge */
  top: anchor(bottom);

  /* Center horizontally with the anchor */
  left: anchor(center);
  translate: -50% 0;
}

Position Above the Anchor

/* Position tooltip above the anchor */
.tooltip {
  position: fixed;
  position-anchor: --tooltip-anchor;

  /* Position the tooltip's bottom edge at the anchor's top edge */
  bottom: anchor(top);
  left: anchor(center);
  translate: -50% 0;
}

Position to the Right

/* Position tooltip to the right of the anchor */
.tooltip {
  position: fixed;
  position-anchor: --tooltip-anchor;

  /* Position the tooltip's left edge at the anchor's right edge */
  left: anchor(right);
  top: anchor(center);
  translate: 0 -50%;
}

Position to the Left

/* Position tooltip to the left of the anchor */
.tooltip {
  position: fixed;
  position-anchor: --tooltip-anchor;

  /* Position the tooltip's right edge at the anchor's left edge */
  right: anchor(left);
  top: anchor(center);
  translate: 0 -50%;
}

Adding Offset/Gap

/* Add spacing between anchor and tooltip */
.tooltip {
  position: fixed;
  position-anchor: --tooltip-anchor;

  /* Use calc() to add offset */
  top: calc(anchor(bottom) + 8px);
  left: anchor(center);
  translate: -50% 0;
}

/* Or use margin */
.tooltip {
  position: fixed;
  position-anchor: --tooltip-anchor;
  top: anchor(bottom);
  left: anchor(center);
  translate: -50% 0;
  margin-top: 8px;
}

4. Anchor Position Options

The anchor() function accepts different keywords to reference various edges and positions of the anchor element.

Physical Position Values

Edge Values

anchor(top) - Top edge of anchor
anchor(bottom) - Bottom edge of anchor
anchor(left) - Left edge of anchor
anchor(right) - Right edge of anchor

Center Values

anchor(center) - Center of anchor (works for both axes)

Percentage-Based Positioning

You can use percentages to position at any point along the anchor's edge.

/* Position at 25% from the left edge of the anchor */
.tooltip {
  left: anchor(25%);
}

/* Position at 75% from the left edge of the anchor */
.tooltip {
  left: anchor(75%);
}

/* Same as anchor(center) */
.tooltip {
  left: anchor(50%);
}

Logical Position Values

For internationalization support, you can use logical properties that adapt to writing direction.

/* Logical values for inline axis (horizontal in LTR) */
anchor(start)   /* Same as left in LTR, right in RTL */
anchor(end)     /* Same as right in LTR, left in RTL */

/* Logical values for block axis (vertical) */
anchor(self-start)   /* Same as top */
anchor(self-end)     /* Same as bottom */

The anchor-size() Function

Use anchor-size() to size an element based on the anchor's dimensions.

/* Match the width of the anchor */
.dropdown-menu {
  position: fixed;
  position-anchor: --dropdown-trigger;
  top: anchor(bottom);
  left: anchor(left);

  /* Set width equal to anchor's width */
  width: anchor-size(width);
}

/* Set minimum width to anchor's width */
.dropdown-menu {
  min-width: anchor-size(width);
}

/* Use anchor's height */
.side-panel {
  height: anchor-size(height);
}

anchor-size() Values

anchor-size(width)- Anchor's width
anchor-size(height)- Anchor's height
anchor-size(block)- Block size (height in horizontal writing)
anchor-size(inline)- Inline size (width in horizontal writing)

5. Fallback Positioning

One of the most powerful features of CSS Anchor Positioning is automatic fallback positioning. When a positioned element would overflow the viewport, the browser can automatically try alternative positions.

The position-try-fallbacks Property

Define fallback positions that the browser should try if the primary position causes overflow.

/* Try flipping to opposite side if needed */
.tooltip {
  position: fixed;
  position-anchor: --my-anchor;

  /* Primary position: below the anchor */
  top: anchor(bottom);
  left: anchor(center);
  translate: -50% 0;

  /* Fallback: flip to above if no room below */
  position-try-fallbacks: flip-block;
}

Built-in Flip Keywords

flip-block

Flips position in the block direction (top/bottom)

flip-inline

Flips position in the inline direction (left/right)

flip-start

Flips start/end positions

flip-block flip-inline

Flips in both directions

The @position-try At-Rule

For more control, define custom fallback positions using @position-try.

/* Define custom fallback positions */
@position-try --bottom-right {
  top: anchor(bottom);
  left: anchor(right);
}

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

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

.tooltip {
  position: fixed;
  position-anchor: --my-anchor;

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

  /* Custom fallbacks in order of preference */
  position-try-fallbacks: --top-center, --left-center, --bottom-right;
}

The position-try-order Property

Control how the browser chooses between fallback options.

/* Try fallbacks based on available space */
.tooltip {
  position-try-order: most-height;  /* Prefer position with most vertical space */
}

.tooltip {
  position-try-order: most-width;   /* Prefer position with most horizontal space */
}

.tooltip {
  position-try-order: most-block-size;  /* Logical: most block space */
}

.tooltip {
  position-try-order: most-inline-size; /* Logical: most inline space */
}

Tip: Combine position-try-fallbacks withposition-try-order to create smart tooltips that always position themselves in the most visible location.

JavaScript Fallback for Unsupported Browsers

// Feature detection for anchor positioning
const supportsAnchorPositioning = CSS.supports('anchor-name', '--test');

if (!supportsAnchorPositioning) {
  // Use Floating UI, Popper.js, or custom positioning
  import('floating-ui').then(({ computePosition }) => {
    // Set up JavaScript-based positioning
  });
}

// Or use CSS @supports
@supports (anchor-name: --test) {
  .tooltip {
    position: fixed;
    position-anchor: --my-anchor;
    top: anchor(bottom);
  }
}

@supports not (anchor-name: --test) {
  .tooltip {
    /* Fallback static positioning */
    position: absolute;
    top: 100%;
    left: 50%;
    transform: translateX(-50%);
  }
}

6. Practical Examples

Tooltip That Follows a Button

A classic tooltip that appears below a button on hover, with automatic flip to top if near the viewport edge.

<!-- HTML -->
<button class="tooltip-trigger">Hover me</button>
<div class="tooltip" popover>
  This is helpful tooltip text!
</div>
/* CSS */
.tooltip-trigger {
  anchor-name: --tooltip-trigger;
}

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

  /* Position below the button */
  top: calc(anchor(bottom) + 8px);
  left: anchor(center);
  translate: -50% 0;

  /* Flip to top if no room below */
  position-try-fallbacks: flip-block;

  /* Styling */
  background: #1f2937;
  color: white;
  padding: 8px 12px;
  border-radius: 6px;
  font-size: 14px;
  white-space: nowrap;

  /* Hide by default */
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s;
}

/* Show tooltip on hover */
.tooltip-trigger:hover + .tooltip,
.tooltip-trigger:focus + .tooltip {
  opacity: 1;
}

Live Demo (Chrome 125+ only)

Hover over the button to see the tooltip. Try scrolling to see fallback positioning.

This is helpful tooltip text!

Dropdown Menu

A dropdown menu that matches the width of its trigger button and positions below it.

<!-- HTML -->
<div class="dropdown">
  <button class="dropdown-trigger" popovertarget="menu">
    Options
    <svg><!-- chevron icon --></svg>
  </button>
  <ul id="menu" class="dropdown-menu" popover>
    <li><a href="#">Profile</a></li>
    <li><a href="#">Settings</a></li>
    <li><a href="#">Sign out</a></li>
  </ul>
</div>
/* CSS */
.dropdown-trigger {
  anchor-name: --dropdown;
}

.dropdown-menu {
  position: fixed;
  position-anchor: --dropdown;

  /* Position below the trigger */
  top: calc(anchor(bottom) + 4px);
  left: anchor(left);

  /* Match the width of the trigger */
  min-width: anchor-size(width);

  /* Flip above if no room below */
  position-try-fallbacks: flip-block;

  /* Styling */
  margin: 0;
  padding: 4px;
  list-style: none;
  background: white;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
}

.dropdown-menu li a {
  display: block;
  padding: 8px 12px;
  color: #374151;
  text-decoration: none;
  border-radius: 4px;
}

.dropdown-menu li a:hover {
  background: #f3f4f6;
}

Live Demo (Chrome 125+ only)

Click the button to toggle the dropdown menu.

Popover Card

A rich popover card with multiple fallback positions for optimal visibility.

<!-- HTML -->
<span class="user-link" popovertarget="user-card">@johndoe</span>
<div id="user-card" class="user-popover" popover>
  <img src="avatar.jpg" alt="John Doe" class="avatar" />
  <h3>John Doe</h3>
  <p>Frontend Developer at Acme Corp</p>
  <button>Follow</button>
</div>
/* CSS */
@position-try --above {
  bottom: calc(anchor(top) + 8px);
  left: anchor(center);
  translate: -50% 0;
}

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

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

.user-link {
  anchor-name: --user-link;
  color: #3b82f6;
  cursor: pointer;
}

.user-popover {
  position: fixed;
  position-anchor: --user-link;

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

  /* Try these positions if primary doesn't fit */
  position-try-fallbacks: --above, --right, --left;

  /* Choose position with most available space */
  position-try-order: most-height;

  /* Styling */
  width: 280px;
  padding: 16px;
  background: white;
  border-radius: 12px;
  box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
  text-align: center;
}

.user-popover .avatar {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  margin-bottom: 8px;
}

.user-popover h3 {
  margin: 0 0 4px;
  font-size: 16px;
  font-weight: 600;
}

.user-popover p {
  margin: 0 0 12px;
  color: #6b7280;
  font-size: 14px;
}

.user-popover button {
  width: 100%;
  padding: 8px 16px;
  background: #3b82f6;
  color: white;
  border: none;
  border-radius: 6px;
  cursor: pointer;
}

Context Menu with Arrow

A context menu with a pointer arrow that adjusts when the menu flips position.

/* CSS */
.context-trigger {
  anchor-name: --context-anchor;
}

.context-menu {
  position: fixed;
  position-anchor: --context-anchor;

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

  position-try-fallbacks: flip-block;

  background: white;
  border-radius: 8px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
  padding: 8px;
}

/* Arrow pointing up (when menu is below) */
.context-menu::before {
  content: '';
  position: absolute;
  top: -6px;
  left: 50%;
  translate: -50% 0;
  border: 6px solid transparent;
  border-bottom-color: white;
}

/* Arrow pointing down (when menu flips above) */
@position-try --flipped {
  bottom: calc(anchor(top) + 8px);
  left: anchor(center);
  translate: -50% 0;
}

/* Rotate arrow when position changes */
.context-menu:has(:popover-open) {
  /* Use :has() or JS to detect flipped state */
}

Quick Reference

CSS Properties

anchor-name: --name
position-anchor: --name
position-try-fallbacks: flip-block
position-try-order: most-height

CSS Functions

anchor(top | bottom | left | right | center)
anchor(50%)
anchor-size(width | height)

Flip Keywords

flip-block - Flip top/bottom
flip-inline - Flip left/right
flip-start - Flip start/end

At-Rules

@position-try --name { ... }

Feature Detection

Always check for browser support before relying on anchor positioning in production.

// JavaScript feature detection
if (CSS.supports('anchor-name', '--test')) {
  console.log('Anchor positioning is supported!');
} else {
  console.log('Falling back to JavaScript positioning');
  // Use Floating UI, Popper.js, or Tippy.js
}

// CSS feature detection
@supports (anchor-name: --test) {
  /* Anchor positioning styles */
}

@supports not (anchor-name: --test) {
  /* Fallback positioning styles */
}