CSS Anchor Positioning: Complete Guide to the New CSS API

Published on

For years, positioning a tooltip next to a button or a dropdown below its trigger required JavaScript. Libraries like Popper.js and Floating UI became staples of modern front-end development just to answer one question: "How do I keep this floating element attached to that other element?" CSS Anchor Positioning is the browser-native answer. It lets you tether any positioned element to any anchor element using pure CSS, complete with automatic fallback positions when space runs out. This guide covers everything you need to start using it today.

1. What Is CSS Anchor Positioning?

CSS Anchor Positioning is a CSS module that introduces a declarative way to position an element relative to one or more anchor elements anywhere in the DOM. Unlike traditional CSS positioning where position: absolute is relative to the nearest positioned ancestor, anchor positioning lets you connect two elements that may be far apart in the document tree.

The core problem it solves: You have a button, and you want a tooltip to appear directly below it. With traditional CSS, you would need the tooltip to be a sibling or child of the button inside a position: relative wrapper. With anchor positioning, the tooltip can live anywhere in the DOM and still be tethered to the button.

/* The anchor: any element you want to tether to */
.my-button {
  anchor-name: --my-button;
}

/* The positioned element: tethered to the anchor */
.my-tooltip {
  position: absolute;
  position-anchor: --my-button;
  top: anchor(bottom);
  left: anchor(center);
  translate: -50% 0;
}

The key properties and functions in this API are:

2. anchor-name and position-anchor

Every anchor positioning relationship starts by declaring an anchor and linking a positioned element to it.

Declaring an anchor

The anchor-name property accepts a dashed-ident (like custom properties). An element can have multiple anchor names, and anchor names must be unique within a document.

/* Single anchor name */
.trigger-button {
  anchor-name: --trigger;
}

/* Multiple anchor names on one element */
.special-element {
  anchor-name: --start-point, --reference;
}

Linking to an anchor

The positioned element uses position-anchor to declare which anchor it follows. The element must use position: absolute or position: fixed.

.popup {
  position: absolute;
  position-anchor: --trigger;
  /* Now use anchor() or inset-area to place it */
}

Implicit anchors with the anchor attribute

HTML also provides an anchor attribute for implicit anchor references, which ties into the Popover API:

<button id="btn" popovertarget="my-popup">Open</button>
<div id="my-popup" popover anchor="btn">
  Popup content here
</div>
/* The anchor attribute creates an implicit anchor relationship */
[popover] {
  position: absolute;
  /* position-anchor is implicitly set via the anchor attribute */
  top: anchor(bottom);
  left: anchor(left);
}

3. The anchor() Function

The anchor() function is used inside inset properties to resolve to a specific edge or position on the anchor element. This is where the real positioning logic lives.

Syntax

/* Full syntax */
anchor(<anchor-name> <anchor-side>, <fallback-value>)

/* Short syntax (uses position-anchor as the anchor reference) */
anchor(<anchor-side>)

/* anchor-side values: top, right, bottom, left, center,
   start, end, self-start, self-end, or a percentage */

Positioning below an anchor

.tooltip {
  position: absolute;
  position-anchor: --my-button;

  /* Top of tooltip aligns with bottom of anchor */
  top: anchor(bottom);

  /* Left of tooltip aligns with left of anchor */
  left: anchor(left);
}

Centering on the anchor

.tooltip-centered {
  position: absolute;
  position-anchor: --my-button;
  top: anchor(bottom);

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

/* Alternative: use left and right together */
.tooltip-centered-alt {
  position: absolute;
  position-anchor: --my-button;
  top: anchor(bottom);
  left: anchor(left);
  right: anchor(right);
  /* Element stretches to match anchor width */
}

Adding gaps with calc()

.tooltip-with-gap {
  position: absolute;
  position-anchor: --my-button;

  /* 8px gap between anchor bottom and tooltip top */
  top: calc(anchor(bottom) + 8px);
  left: anchor(center);
  translate: -50% 0;
}

Positioning above

.tooltip-above {
  position: absolute;
  position-anchor: --my-button;

  /* Bottom of tooltip aligns with top of anchor, minus 8px gap */
  bottom: calc(anchor(top) - 8px);
  left: anchor(center);
  translate: -50% 0;
}

Positioning to the side

.side-panel {
  position: absolute;
  position-anchor: --sidebar-trigger;

  /* Left edge of panel at right edge of anchor */
  left: calc(anchor(right) + 12px);

  /* Vertically align tops */
  top: anchor(top);
}

Using percentages

.custom-position {
  position: absolute;
  position-anchor: --my-element;

  /* Position at 25% from the top of the anchor */
  top: anchor(25%);
  left: anchor(right);
}

4. inset-area for Simplified Placement

While the anchor() function gives you precise control, inset-area provides a simpler grid-based approach. It divides the space around the anchor into a 3x3 grid and lets you place the element in one or more regions.

+------------------+------------------+------------------+
|   top / left     |   top / center   |   top / right    |
+------------------+------------------+------------------+
| center / left    |     (anchor)     | center / right   |
+------------------+------------------+------------------+
| bottom / left    | bottom / center  | bottom / right   |
+------------------+------------------+------------------+

Basic usage

/* Place below the anchor, centered */
.tooltip {
  position: absolute;
  position-anchor: --my-button;
  inset-area: bottom center;
}

/* Place above the anchor */
.tooltip-above {
  position: absolute;
  position-anchor: --my-button;
  inset-area: top center;
}

/* Place to the right */
.side-element {
  position: absolute;
  position-anchor: --trigger;
  inset-area: center right;
}

Spanning regions

/* Span the entire bottom row */
.wide-panel {
  position: absolute;
  position-anchor: --header;
  inset-area: bottom span-all;
}

/* Span the right column top to bottom */
.sidebar {
  position: absolute;
  position-anchor: --main-content;
  inset-area: span-all right;
}

inset-area vs anchor()

Use inset-area when you want quick, grid-based placement without worrying about exact pixel positioning. Use anchor() when you need precise control over gaps, offsets, or non-grid-aligned positions. You can even combine them: use inset-area for the general region, then add margin for fine-tuning.

/* inset-area for placement, margin for gap */
.tooltip {
  position: absolute;
  position-anchor: --button;
  inset-area: bottom center;
  margin-top: 8px; /* adds gap between anchor and tooltip */
}

5. position-try and @position-try Fallbacks

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

Built-in flip keywords

.tooltip {
  position: absolute;
  position-anchor: --button;
  inset-area: top center;

  /* If top overflows, try flipping to bottom */
  position-try-fallbacks: flip-block;
}

/* flip-block:  flips in the block axis (top <-> bottom) */
/* flip-inline: flips in the inline axis (left <-> right) */
/* flip-block flip-inline: flips in both axes */

Custom fallback positions with @position-try

/* Define custom fallback positions */
@position-try --below {
  inset-area: bottom center;
  margin-top: 8px;
}

@position-try --above {
  inset-area: top center;
  margin-bottom: 8px;
}

@position-try --right {
  inset-area: center right;
  margin-left: 8px;
}

@position-try --left {
  inset-area: center left;
  margin-right: 8px;
}

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

  /* Default position */
  inset-area: bottom center;
  margin-top: 8px;

  /* Try these positions in order if default overflows */
  position-try-fallbacks: --above, --right, --left;
}

Combining built-in flips with custom positions

.dropdown-menu {
  position: absolute;
  position-anchor: --dropdown-trigger;
  inset-area: bottom span-left;
  margin-top: 4px;

  /* First try flipping vertically, then try custom positions */
  position-try-fallbacks: flip-block, --compact-right;
}

@position-try --compact-right {
  inset-area: center right;
  margin-left: 4px;
  max-height: 300px;
  overflow-y: auto;
}

position-try-order

The position-try-order property controls which fallback is preferred when multiple options fit. The value most-height or most-width picks the position that gives the element the most available space:

.tooltip {
  position: absolute;
  position-anchor: --button;
  inset-area: bottom center;
  position-try-fallbacks: flip-block, --right, --left;

  /* Prefer the position that gives the most vertical space */
  position-try-order: most-height;
}

6. anchor-size() for Relative Sizing

The anchor-size() function lets you size a positioned element relative to its anchor. This is useful for dropdowns that should match the width of their trigger, or tooltips that scale with their anchor.

/* anchor-size() values: width, height, block, inline,
   self-block, self-inline */

/* Dropdown matches anchor width */
.dropdown-menu {
  position: absolute;
  position-anchor: --select-trigger;
  inset-area: bottom center;
  width: anchor-size(width);
}

/* Minimum width matches anchor, but can grow */
.autocomplete-results {
  position: absolute;
  position-anchor: --search-input;
  inset-area: bottom center;
  min-width: anchor-size(width);
  max-width: 500px;
}

/* Use in calc() */
.wide-panel {
  position: absolute;
  position-anchor: --card;
  inset-area: bottom center;
  width: calc(anchor-size(width) + 2rem);
}

7. Building Tooltips Without JavaScript

This is the use case that excites most developers. A fully functional tooltip with zero JavaScript, including viewport-aware repositioning.

HTML structure

<div class="tooltip-wrapper">
  <button class="tooltip-trigger" style="anchor-name: --tip1">
    Hover me
  </button>
  <div class="tooltip" style="position-anchor: --tip1">
    This is a helpful tooltip with more information.
  </div>
</div>

CSS for the tooltip

.tooltip-trigger {
  anchor-name: --tip1;
}

.tooltip {
  /* Hidden by default */
  display: none;

  /* Positioning */
  position: absolute;
  position-anchor: --tip1;
  inset-area: top center;
  margin-bottom: 8px;

  /* Fallback if top overflows */
  position-try-fallbacks: flip-block;

  /* Styling */
  background: #1a1d27;
  color: #e4e4e7;
  padding: 0.5rem 0.75rem;
  border-radius: 6px;
  border: 1px solid #2a2e3a;
  font-size: 0.875rem;
  max-width: 250px;
  width: max-content;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
  z-index: 100;
}

/* Show on hover and focus */
.tooltip-trigger:hover + .tooltip,
.tooltip-trigger:focus + .tooltip {
  display: block;
}

/* Arrow using pseudo-element */
.tooltip::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  translate: -50% 0;
  border: 6px solid transparent;
  border-top-color: #1a1d27;
}

Multiple tooltips on one page

/* Each button-tooltip pair uses a unique anchor name */
.btn-save   { anchor-name: --save; }
.btn-delete { anchor-name: --delete; }
.btn-edit   { anchor-name: --edit; }

.tooltip-save   { position-anchor: --save; }
.tooltip-delete { position-anchor: --delete; }
.tooltip-edit   { position-anchor: --edit; }

/* Shared tooltip styles apply to all */
[class^="tooltip-"] {
  position: absolute;
  inset-area: top center;
  margin-bottom: 8px;
  position-try-fallbacks: flip-block;
  /* ... styling ... */
}

Dropdown menus are a natural fit for anchor positioning. The menu needs to appear below the trigger, match its width (or exceed it), and reposition if it would overflow the viewport.

<nav class="main-nav">
  <div class="nav-item">
    <button class="nav-button" style="anchor-name: --products">
      Products
    </button>
    <ul class="dropdown" style="position-anchor: --products">
      <li><a href="/analytics">Analytics</a></li>
      <li><a href="/automation">Automation</a></li>
      <li><a href="/integrations">Integrations</a></li>
      <li><a href="/security">Security</a></li>
    </ul>
  </div>
</nav>
.nav-button {
  anchor-name: --products;
}

.dropdown {
  display: none;
  position: absolute;
  position-anchor: --products;

  /* Place below, left-aligned */
  top: anchor(bottom);
  left: anchor(left);
  margin-top: 4px;

  /* Match at least the anchor width */
  min-width: anchor-size(width);

  /* Reposition if near bottom of viewport */
  position-try-fallbacks: flip-block;

  /* Styling */
  background: #1a1d27;
  border: 1px solid #2a2e3a;
  border-radius: 8px;
  padding: 0.5rem 0;
  list-style: none;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
  z-index: 200;
}

/* Show dropdown on hover */
.nav-item:hover .dropdown,
.nav-button:focus + .dropdown {
  display: block;
}

.dropdown li a {
  display: block;
  padding: 0.5rem 1rem;
  color: #e4e4e7;
  text-decoration: none;
  white-space: nowrap;
}

.dropdown li a:hover {
  background: rgba(59, 130, 246, 0.1);
  color: #3b82f6;
}

9. Popovers with Popover API + Anchor Positioning

The Popover API and CSS Anchor Positioning are designed to work together. The Popover API handles showing/hiding, focus management, and light-dismiss behavior. Anchor positioning handles where the popover appears.

<button id="info-btn" popovertarget="info-popup">
  More info
</button>

<div id="info-popup" popover anchor="info-btn">
  <h3>Additional Information</h3>
  <p>This popover is positioned with CSS Anchor Positioning
     and managed by the Popover API. Click outside to dismiss.</p>
</div>
/* The anchor attribute provides an implicit anchor */
#info-btn {
  anchor-name: --info-btn;
}

#info-popup {
  /* Popover defaults to position: fixed in the top layer */
  position-anchor: --info-btn;
  inset-area: bottom center;
  margin-top: 8px;

  /* Fallback positions */
  position-try-fallbacks: flip-block, flip-inline;

  /* Styling */
  background: #1a1d27;
  border: 1px solid #2a2e3a;
  border-radius: 8px;
  padding: 1rem 1.25rem;
  max-width: 320px;
  color: #e4e4e7;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
}

/* Animate popover entrance */
#info-popup:popover-open {
  opacity: 1;
  transform: translateY(0);
}

/* Starting state for animation */
@starting-style {
  #info-popup:popover-open {
    opacity: 0;
    transform: translateY(-8px);
  }
}

#info-popup {
  transition: opacity 0.2s, transform 0.2s,
              display 0.2s allow-discrete,
              overlay 0.2s allow-discrete;
}

The anchor HTML attribute creates an implicit anchor relationship, so you do not strictly need position-anchor in CSS. However, explicitly setting it gives you more control and makes the relationship clearer.

For popovers with interactive content (forms, checkboxes, buttons), the pattern is identical. The Popover API ensures focus trapping and light-dismiss, while anchor positioning keeps the popover visually attached to its trigger regardless of scroll position.

10. Responsive Anchor Positioning

Anchor positioning works with media queries and container queries, letting you change positioning behavior at different viewport sizes.

/* Default: tooltip below on mobile */
.tooltip {
  position: absolute;
  position-anchor: --trigger;
  inset-area: bottom center;
  margin-top: 8px;
  position-try-fallbacks: flip-block;
}

/* Desktop: tooltip to the right */
@media (min-width: 768px) {
  .tooltip {
    inset-area: center right;
    margin-top: 0;
    margin-left: 8px;
    position-try-fallbacks: flip-inline, flip-block;
  }
}

Adapting to container size

/* In a narrow container, stack vertically */
@container (max-width: 400px) {
  .info-popup {
    inset-area: bottom span-all;
    margin-top: 4px;
    width: anchor-size(width);
  }
}

/* In a wide container, position to the side */
@container (min-width: 401px) {
  .info-popup {
    inset-area: center right;
    margin-left: 12px;
    width: 280px;
  }
}

Combining with scroll-driven positioning

Anchor positioning automatically recalculates as the anchor moves during scroll. This means anchored elements follow their anchor naturally when the page scrolls, without any extra JavaScript or scroll event listeners.

11. Multiple Anchors

A positioned element can reference multiple anchors simultaneously. This is useful for connecting elements, spanning between references, or building complex layouts.

.start-marker { anchor-name: --start; }
.end-marker   { anchor-name: --end; }

/* A connector line that spans between two anchors */
.connector {
  position: absolute;

  /* Left edge at the right side of the start marker */
  left: anchor(--start right);

  /* Right edge at the left side of the end marker */
  right: anchor(--end left);

  /* Vertically centered between both */
  top: anchor(--start center);
  height: 2px;
  background: #3b82f6;
}

Positioning between anchors

.header-section { anchor-name: --header; }
.footer-section { anchor-name: --footer; }

/* A floating element positioned between header and footer */
.floating-cta {
  position: fixed;
  left: anchor(--header center);
  translate: -50% 0;

  /* Vertically between the two anchors */
  top: calc(
    (anchor(--header bottom) + anchor(--footer top)) / 2
  );
}

Fallback anchor references

When using the explicit anchor() syntax with a named anchor, you can provide a fallback value if the anchor is not found:

.popup {
  position: absolute;
  /* If --primary-anchor is not found, fall back to 100px */
  top: anchor(--primary-anchor bottom, 100px);
  left: anchor(--primary-anchor left, 50px);
}

12. Browser Support and Polyfills

CSS Anchor Positioning is available in Chromium browsers and making its way to other engines:

Feature detection

/* Detect anchor positioning support */
@supports (anchor-name: --test) {
  .tooltip {
    position: absolute;
    position-anchor: --trigger;
    inset-area: bottom center;
  }
}

/* Fallback for unsupported browsers */
@supports not (anchor-name: --test) {
  .tooltip-wrapper {
    position: relative;
  }
  .tooltip {
    position: absolute;
    top: 100%;
    left: 50%;
    transform: translateX(-50%);
    margin-top: 8px;
  }
}

JavaScript feature detection

// Check if CSS Anchor Positioning is supported
const supportsAnchor = CSS.supports("anchor-name", "--test");

if (!supportsAnchor) {
  // Load polyfill or use Floating UI fallback
  import("@oddbird/css-anchor-positioning/fn")
    .then(({ default: polyfill }) => polyfill());
}

The Oddbird polyfill

The @oddbird/css-anchor-positioning polyfill provides a JavaScript implementation for browsers without native support. Install it via npm:

npm install @oddbird/css-anchor-positioning
// Apply polyfill only when needed
if (!CSS.supports("anchor-name", "--test")) {
  import("@oddbird/css-anchor-positioning").then(
    ({ apply }) => apply()
  );
}

The polyfill parses your CSS, finds anchor positioning rules, and applies equivalent JavaScript positioning. It supports anchor-name, position-anchor, anchor(), inset-area, position-try-fallbacks, and anchor-size().

13. Comparison with JavaScript Positioning

For years, libraries like Popper.js (now Floating UI) have been the standard for floating element positioning. Here is how CSS Anchor Positioning compares:

Feature CSS Anchor Positioning Floating UI / Popper.js
Bundle size 0 KB (native CSS) ~3-8 KB min+gzip
JavaScript required No Yes
Fallback positions position-try-fallbacks flip middleware
Scroll handling Automatic (browser) autoUpdate listener
Resize handling Automatic (browser) autoUpdate + size middleware
Performance Composited by browser JS layout thrashing possible
Browser support Chromium 125+ (growing) All browsers
Framework integration Framework-agnostic CSS React/Vue/Svelte wrappers
Custom middleware Limited to CSS capabilities Extensible middleware system
Virtual elements No (requires DOM element) Yes (cursor position, etc.)

When to use CSS Anchor Positioning

When to stick with Floating UI

Progressive enhancement strategy

// Use CSS Anchor Positioning when available,
// fall back to Floating UI
async function setupTooltip(trigger, tooltip) {
  if (CSS.supports("anchor-name", "--test")) {
    // Browser handles it natively via CSS
    trigger.style.anchorName = "--tooltip-anchor";
    tooltip.style.positionAnchor = "--tooltip-anchor";
    return;
  }

  // Fallback to Floating UI
  const { computePosition, flip, offset } =
    await import("@floating-ui/dom");

  function update() {
    computePosition(trigger, tooltip, {
      placement: "bottom",
      middleware: [offset(8), flip()],
    }).then(({ x, y }) => {
      tooltip.style.left = `${x}px`;
      tooltip.style.top = `${y}px`;
    });
  }

  update();
}

14. Frequently Asked Questions

What is CSS Anchor Positioning and when should I use it?

CSS Anchor Positioning is a new CSS API that lets you tether an absolutely positioned element to one or more anchor elements on the page using pure CSS. You should use it whenever you need tooltips, popovers, dropdown menus, floating labels, or any UI where one element must stay visually connected to another. Before this API, developers relied on JavaScript libraries like Popper.js or Floating UI to calculate positions, watch for scroll and resize events, and flip elements when they would overflow the viewport. CSS Anchor Positioning handles all of this declaratively in the browser, with automatic fallback positioning via position-try-fallbacks, and it works seamlessly with the Popover API for accessible disclosure patterns.

How does anchor() function work in CSS?

The anchor() function is used inside inset properties (top, right, bottom, left, and their logical equivalents) to resolve to a position on an anchor element. The syntax is anchor(anchor-side), where anchor-side can be top, right, bottom, left, center, or a percentage. For example, top: anchor(bottom) positions the top edge of the positioned element at the bottom edge of the anchor. You can combine it with calc() for offsets, such as top: calc(anchor(bottom) + 8px) to add an 8-pixel gap. The function only works on elements with position: absolute or position: fixed that have a position-anchor property or are linked to an anchor via the anchor-name and position-anchor pair.

What is the browser support for CSS Anchor Positioning?

As of early 2026, CSS Anchor Positioning is supported in Chrome 125+ (released May 2024), Edge 125+, and Opera 111+. Safari has begun implementing it behind a feature flag in Safari Technology Preview. Firefox has it listed on its standards positions page as worth prototyping and has started implementation. For browsers that do not yet support it, you can use the CSS Anchor Positioning polyfill maintained by Oddbird, which provides a JavaScript fallback. Use @supports (anchor-name: --x) to feature-detect support and provide progressive enhancement or fallback positioning.

Related Resources

📐 CSS Grid Complete Guide
Master two-dimensional layouts with CSS Grid
🎯 CSS :has() Selector Guide
The parent selector CSS developers have wanted for years
📦 CSS Container Queries
Responsive components based on container size, not viewport