The Problem with Global Anchor Names in Reusable Components

CSS Anchor Positioning is a game-changer for building tooltips, dropdowns, and popovers natively in CSS without relying on JavaScript libraries like Floating UI or Popper.js. However, when working with component-based architectures (like React, Vue, Web Components, or server-rendered templates), a common snag arises: anchor names are global by default.

If you assign anchor-name: --menu-anchor; to a button, any element using position-anchor: --menu-anchor; will match the last element rendered in the DOM tree that defined that name. Generating unique IDs or arbitrary CSS custom identifiers like --menu-anchor-1, --menu-anchor-2 via JavaScript ruins the elegance of declarative CSS styling.

Fortunately, the CSS Anchor Positioning specification introduces solutions tailored specifically for scoping and implicit relationships.

Solution 1: The Native CSS anchor-scope Property

The CSS Anchor Positioning specification defines a property called anchor-scope specifically designed to isolate anchor names within a subtree. This acts like a barrier: elements inside the scope can only anchor to targets defined within that same subtree.

/* Scope the anchor name to the component wrapper */
.item {
  anchor-scope: --menu-anchor;
}

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

.menu {
  position: absolute;
  position-anchor: --menu-anchor;
  position-area: bottom;
}

With anchor-scope: --menu-anchor; on .item, the anchor query won't leak outside or reference an element from an adjacent .item. Each popover finds the --menu-anchor defined inside its own wrapper container.

Browser support note: As of early 2025, anchor-scope is specified in the CSS Anchor Positioning Module Level 1 and is being rolled out across Chromium-based browsers. If you are targeting modern browsers, this is the cleanest, purest CSS-only solution.

Solution 2: Implicit Anchoring with popovertarget (HTML-Driven)

If you're already using the native Popover API (as indicated by the popover attribute), you might not even need an explicit anchor-name or anchor-scope at all.

The HTML and CSS specs allow for implicit anchor positioning. When an invoker button points to a popover via popovertarget, modern browsers automatically establish an implicit anchor relationship between the trigger and the popover.

<div class="item">
  <!-- Give each popover an id, which is standard HTML for popover targets -->
  <button class="trigger" popovertarget="menu-1">Options</button>
  <div id="menu-1" class="menu" popover>
    Edit<br>
    Delete
  </div>
</div>

In CSS, you can omit anchor-name and position-anchor completely. You simply use the anchor positioning properties:

.menu {
  /* The anchor is implicitly derived from the invoking button */
  position-area: bottom span-right;
  margin: 0.25rem 0 0 0;
}

Because the popover knows which button toggled it through popovertarget, CSS positions the menu relative to that exact invoker automatically, removing the need for CSS custom ident names altogether.

Solution 3: Scoped CSS Custom Properties with Inline Style Identifiers

If you cannot use anchor-scope due to browser compatibility constraints and you need custom positioning unrelated to the popovertarget invoker, a lightweight fallback is scoping an inline anchor name via CSS variables or crypto.randomUUID() / incremental IDs generated at the component boundary:

<div class="item" style="--anchor: --menu-1;">
  <button class="trigger">Options</button>
  <div class="menu" popover>Edit</div>
</div>
.trigger {
  anchor-name: var(--anchor);
}

.menu {
  position: absolute;
  position-anchor: var(--anchor);
  position-area: bottom;
}

While this requires passing a variable into the template, your stylesheets remain 100% DRY, static, and reusable.

Summary: Which Approach Should You Choose?

  • Best CSS-Only Approach: Use anchor-scope: --your-anchor-name; on the component container.
  • Best Accessibility & Popover Pattern: Use popovertarget to leverage native implicit anchoring without any anchor names.
  • Best Backward Compatibility: Set a dynamic CSS custom property inline on the component wrapper and reference it using var(--anchor).