How to Scope CSS Anchor Positioning to Components Without Unique Names
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
popovertargetto 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).