← Back to DevBytes

Implementing CSS Anchor Positioning in Modern Web Applications

What is CSS Anchor Positioning?

CSS Anchor Positioning is a modern CSS specification that allows you to position an element relative to another "anchor" element on the page — without writing a single line of JavaScript. It introduces the anchor() function, the anchor-size() function, and the position-anchor property, giving developers a declarative way to tether popovers, tooltips, dropdown menus, and floating UI components to their reference elements.

In traditional web development, positioning a tooltip next to a button requires calculating bounding rectangles with getBoundingClientRect(), accounting for viewport overflow, and manually updating positions on scroll or resize. CSS Anchor Positioning eliminates all of that boilerplate by letting the browser engine handle the math natively.

Core Terminology

Why CSS Anchor Positioning Matters

🚀 Deploy your AI agent in 10 minutes

Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.

Try it free →

Before anchor positioning, developers faced several persistent challenges when building floating UI elements:

CSS Anchor Positioning solves these problems at the browser level. The engine handles collision detection, scroll-driven repositioning, and even automatic fallback positioning — all declaratively. This means less JavaScript, better performance (since layout calculations run on the compositor thread), and cleaner, more maintainable codebases.

How to Use CSS Anchor Positioning

Step 1: Define an Anchor Element

First, you give the anchor element a name using the anchor-name property. This name is a custom dashed-identifier (like a CSS variable) that will be referenced by the positioned element.

<button class="anchor-btn">Hover me</button>
<div class="tooltip">This is a tooltip tethered to the button</div>
.anchor-btn {
  anchor-name: --btn-anchor;
}

.tooltip {
  position: absolute;
  position-anchor: --btn-anchor;
  /* The tooltip will now position itself relative to the button */
}

The anchor-name property accepts a single dashed-ident or none. An element can have only one anchor name, but multiple positioned elements can reference the same anchor.

Step 2: Position Using the anchor() Function

The anchor() function resolves to a pixel value representing the position of a specific edge or corner of the anchor element. You use it inside positioning properties like top, bottom, left, right, or even inset logical properties.

The function accepts two arguments:

.tooltip {
  position: absolute;
  position-anchor: --btn-anchor;
  
  /* Position the tooltip's top edge at the anchor's bottom edge */
  top: anchor(--btn-anchor bottom, 0px);
  
  /* Center the tooltip horizontally relative to the anchor */
  left: anchor(--btn-anchor center);
  transform: translateX(-50%);
}

Step 3: Using Logical Edge Keywords

The anchor() function supports physical edges (top, bottom, left, right) as well as logical edges (start, end, block-start, block-end, inline-start, inline-end) and percentage values for interior positioning.

.dropdown-menu {
  position: absolute;
  position-anchor: --menu-trigger;
  
  /* Align the menu's inline-start with the anchor's inline-start */
  inset-inline-start: anchor(--menu-trigger inline-start);
  
  /* Place the menu below the anchor */
  inset-block-start: anchor(--menu-trigger block-end);
}

Complete Tooltip Example

Here is a fully functional tooltip implementation using only CSS:

<div class="container">
  <button class="anchor-button">
    Delete account
    <span class="tooltip">This action cannot be undone</span>
  </button>
</div>
.container {
  position: relative;
  display: inline-block;
}

.anchor-button {
  anchor-name: --delete-tooltip-anchor;
  position: relative;
  padding: 10px 20px;
  background: #e53935;
  color: white;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  font-size: 16px;
}

.tooltip {
  position: absolute;
  position-anchor: --delete-tooltip-anchor;
  
  /* Position below the button with a small gap */
  top: anchor(--delete-tooltip-anchor bottom, 0px);
  margin-top: 8px;
  
  /* Center horizontally */
  left: anchor(--delete-tooltip-anchor center);
  transform: translateX(-50%);
  
  /* Styling */
  background: #333;
  color: #fff;
  padding: 8px 14px;
  border-radius: 4px;
  font-size: 14px;
  white-space: nowrap;
  pointer-events: none;
  
  /* Hidden by default, shown on hover */
  opacity: 0;
  transition: opacity 0.2s ease;
}

.anchor-button:hover .tooltip,
.anchor-button:focus-visible .tooltip {
  opacity: 1;
}

Working with anchor-size()

The companion anchor-size() function lets you match the dimensions of the positioned element to the anchor element. This is incredibly useful for dropdown menus that should match the width of their trigger button, or for combo boxes where the popup list should align perfectly.

The function accepts size keywords:

.dropdown-menu {
  position: absolute;
  position-anchor: --dropdown-trigger;
  
  /* Match the width of the anchor element */
  width: anchor-size(--dropdown-trigger width, 200px);
  
  /* But never shrink below 150px */
  min-width: 150px;
  
  /* Position below the anchor */
  top: anchor(--dropdown-trigger bottom, 0px);
  left: anchor(--dropdown-trigger left);
}

Advanced Size Matching Example

.combo-box-popup {
  position: absolute;
  position-anchor: --combobox-input;
  
  /* Match the anchor's width but cap it */
  width: min(
    anchor-size(--combobox-input width, 300px),
    500px
  );
  
  /* Also match the minimum width constraint */
  min-width: anchor-size(--combobox-input min-width, 200px);
  
  top: anchor(--combobox-input bottom, 0px);
  left: anchor(--combobox-input left);
  
  background: white;
  border: 1px solid #ccc;
  border-radius: 0 0 8px 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  max-height: 300px;
  overflow-y: auto;
}

Automatic Fallback Positioning with @position-fallback

One of the most powerful features of CSS Anchor Positioning is the ability to define fallback position rules. When the preferred position would cause the element to overflow the viewport (or its containing block), the browser automatically tries alternative positions in the order you specify.

Fallback sets are defined using the @position-fallback at-rule, and applied with the position-fallback property.

@position-fallback --tooltip-fallbacks {
  /* First choice: centered below */
  @try {
    top: anchor(--btn-anchor bottom);
    left: anchor(--btn-anchor center);
    transform: translateX(-50%);
    margin-top: 8px;
  }
  
  /* Second choice: centered above */
  @try {
    bottom: anchor(--btn-anchor top);
    left: anchor(--btn-anchor center);
    transform: translateX(-50%);
    margin-bottom: 8px;
  }
  
  /* Third choice: to the right */
  @try {
    top: anchor(--btn-anchor top);
    left: anchor(--btn-anchor right);
    margin-left: 8px;
  }
  
  /* Last resort: to the left */
  @try {
    top: anchor(--btn-anchor top);
    right: anchor(--btn-anchor left);
    margin-right: 8px;
  }
}

.tooltip {
  position: absolute;
  position-anchor: --btn-anchor;
  position-fallback: --tooltip-fallbacks;
  
  background: #333;
  color: white;
  padding: 8px 14px;
  border-radius: 4px;
}

Each @try block defines a complete positioning strategy. The browser evaluates them in order and picks the first one that fits entirely within the containing block without overflow. This happens automatically on scroll, resize, or any layout change — no JavaScript required.

Using the Popover API with Anchor Positioning

The combination of the Popover API and CSS Anchor Positioning is particularly elegant. Popovers automatically appear in the top layer (above all other content), and anchor positioning tethers them to their invoking element.

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

<div id="main-menu" class="menu" popover>
  <ul>
    <li><a href="#">Profile</a></li>
    <li><a href="#">Settings</a></li>
    <li><a href="#">Sign out</a></li>
  </ul>
</div>
.trigger {
  anchor-name: --menu-anchor;
  padding: 10px 18px;
  background: #4a6cf7;
  color: white;
  border: none;
  border-radius: 6px;
  cursor: pointer;
}

.menu {
  position: absolute;
  position-anchor: --menu-anchor;
  position-fallback: --menu-fallbacks;
  
  /* Default: below the trigger, aligned to left edge */
  top: anchor(--menu-anchor bottom);
  left: anchor(--menu-anchor left);
  margin-top: 6px;
  
  /* Match trigger width */
  width: anchor-size(--menu-anchor width, 200px);
  
  background: white;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
  padding: 6px 0;
  margin: 0; /* Reset popover margin */
}

.menu ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

.menu li a {
  display: block;
  padding: 10px 16px;
  color: #333;
  text-decoration: none;
  transition: background 0.15s;
}

.menu li a:hover {
  background: #f5f5f5;
}

@position-fallback --menu-fallbacks {
  @try {
    top: anchor(--menu-anchor bottom);
    left: anchor(--menu-anchor left);
    margin-top: 6px;
  }
  @try {
    bottom: anchor(--menu-anchor top);
    left: anchor(--menu-anchor left);
    margin-bottom: 6px;
  }
  @try {
    top: anchor(--menu-anchor bottom);
    right: anchor(--menu-anchor right);
    margin-top: 6px;
  }
}

Dynamic Anchor Visibility with visibility: hidden and anchor-visibility

Sometimes the anchor element may be off-screen or hidden. The anchor-visibility property on the positioned element controls what happens in that case. Combined with the visibility property on the anchor, you can create sophisticated show/hide logic.

.anchor-trigger {
  anchor-name: --dynamic-anchor;
}

.floating-panel {
  position: absolute;
  position-anchor: --dynamic-anchor;
  top: anchor(--dynamic-anchor bottom);
  left: anchor(--dynamic-anchor left);
  
  /* Hide this panel when the anchor is off-screen */
  anchor-visibility: hidden;
}

When anchor-visibility is set to hidden, the positioned element disappears if the anchor element (or the specific anchor edges being referenced) is not visible within the viewport. The default value is visible, which keeps the positioned element visible regardless.

Browser Support and Progressive Enhancement

As of early 2025, CSS Anchor Positioning is available in:

For browsers without support, you should implement a JavaScript fallback. The recommended approach is to use feature detection and fall back to a positioning library like Floating UI.

/* Feature detection for anchor positioning */
@supports (anchor-name: --test) or (position-anchor: --test) {
  .tooltip {
    /* Use native anchor positioning */
    position: absolute;
    position-anchor: --btn-anchor;
    top: anchor(--btn-anchor bottom);
    left: anchor(--btn-anchor center);
    transform: translateX(-50%);
  }
}

/* Fallback styles for unsupported browsers */
@supports not ((anchor-name: --test) or (position-anchor: --test)) {
  .tooltip {
    /* JavaScript will handle positioning via data attributes or inline styles */
    position: absolute;
    /* These values will be set dynamically by JS */
  }
}

On the JavaScript side, you can detect support and apply a polyfill strategy:

function supportsAnchorPositioning() {
  return CSS.supports('anchor-name', '--test') || 
         CSS.supports('position-anchor', '--test');
}

if (!supportsAnchorPositioning()) {
  // Initialize Floating UI or custom positioning logic
  document.querySelectorAll('.tooltip').forEach(tooltip => {
    const anchor = tooltip.closest('[class*="anchor"]');
    if (anchor) {
      // Use getBoundingClientRect and manual positioning
      positionTooltipFallback(anchor, tooltip);
    }
  });
}

Best Practices

1. Use Descriptive Anchor Names

Name your anchors meaningfully to make the relationship clear in your CSS. A naming convention like --component-name-anchor works well in larger codebases.

/* Good */
.search-input { anchor-name: --search-input-anchor; }
.profile-avatar { anchor-name: --profile-avatar-anchor; }

/* Avoid vague names */
.some-btn { anchor-name: --anchor1; } /* Poor */

2. Always Provide Fallback Values

The second argument of anchor() and anchor-size() is a fallback that takes effect when the anchor name doesn't resolve. This prevents your positioned element from ending up at 0,0 or with zero dimensions.

.tooltip {
  /* Always include a fallback */
  top: anchor(--my-anchor bottom, 16px);
  left: anchor(--my-anchor center, 50%);
  width: anchor-size(--my-anchor width, 250px);
}

3. Combine with @position-fallback for Robustness

Never ship a positioned element without fallback positioning rules. Viewports vary dramatically, and the anchor may be near any edge of the screen. A @position-fallback set with at least 2–3 alternatives covers most scenarios.

4. Account for the Top Layer with Popovers

When using the Popover API, remember that popovers render in the top layer, which sits above all other content including fixed-position elements. Anchor positioning works seamlessly with the top layer, but be mindful of z-index conflicts when mixing anchored non-popover elements with other content.

5. Test with Dynamic Content Changes

Anchor positioning recalculates automatically when the anchor moves (due to scroll, layout shift, or content change). Test scenarios where the anchor changes size — for example, when text wraps on resize or when an image loads and shifts layout. The browser handles these correctly, but your fallback rules should cover the expanded size range.

6. Use Logical Properties for Internationalization

Prefer logical edge keywords (block-start, inline-end, etc.) over physical ones when your application supports right-to-left languages or vertical writing modes.

.rtl-tooltip {
  position: absolute;
  position-anchor: --rtl-anchor;
  /* Works correctly in both LTR and RTL contexts */
  inset-block-start: anchor(--rtl-anchor block-end);
  inset-inline-start: anchor(--rtl-anchor inline-start);
}

7. Keep Anchor Chains Simple

While you can nest positioned elements (a tooltip anchored to a menu that is itself anchored to a button), deep chains become hard to debug. Keep anchor relationships shallow — ideally one level of indirection.

8. Leverage CSS Transitions and Animations

Since anchor positioning values are resolved by the browser as actual pixel lengths, you can animate between positions smoothly:

.tooltip {
  position: absolute;
  position-anchor: --btn-anchor;
  top: anchor(--btn-anchor bottom);
  left: anchor(--btn-anchor center);
  transform: translateX(-50%) translateY(0);
  
  transition: transform 0.3s ease, opacity 0.3s ease;
}

.tooltip.exit {
  transform: translateX(-50%) translateY(8px);
  opacity: 0;
}

Common Pitfalls and Solutions

Pitfall: Anchor Name Scoping

Anchor names are not scoped to a parent container — they are global across the document. If two elements share the same anchor name, the positioned element will anchor to the nearest one in DOM order (or the last one declared). Use unique names to avoid conflicts.

Pitfall: Forgetting the Containing Block

Positioned elements using position: absolute need a containing block that encompasses both the anchor and the expected position of the floating element. If the anchor is inside an overflow: hidden container, the positioned element may be clipped. Consider using the Popover API (which escapes overflow clipping via the top layer) or restructuring your DOM.

Pitfall: Anchor on Inline Elements

Anchor positioning works on any element, but for inline elements, the anchor rectangle is based on the line box fragments. For precise control, use block-level or inline-block elements as anchors.

Real-World Use Cases

Conclusion

CSS Anchor Positioning represents a significant evolution in how we build floating UI components for the web. By moving positioning logic from JavaScript into the browser's layout engine, we gain performance benefits, simpler code, and automatic handling of edge cases like viewport overflow and scroll-driven repositioning. The combination of anchor-name, the anchor() function, anchor-size(), and @position-fallback gives developers a complete declarative toolkit for tethering elements together.

While browser support is still expanding, the feature is ready for production use in Chromium-based browsers today, and progressive enhancement strategies allow you to ship anchored interfaces while providing solid JavaScript fallbacks for other engines. Start adopting CSS Anchor Positioning in your tooltips, menus, and popups — you'll write less code, ship fewer kilobytes of positioning logic, and let the browser handle the hard parts for you.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles