Also available in Español
The Claim Being Tested
Tuesday's essay argued that implicit anchoring already solves the ordinary popover case for free. A popover tethered to its own trigger needs no naming, no scoping, nothing. The gap only opens when a real requirement steps outside that case, and even then, only in combination with something easy to miss: DOM structure inherited from before the platform solved overlay clipping, when overlays had to live apart from whatever triggered them.
This Note reproduces that exact combination — a shared anchor name, a popover sitting as a sibling instead of a descendant — across a real multi-row grid, and confirms whether scoping actually fixes every row, not just the one that happened to get tested.
Watch It Break
Four rows, each with its own action menu, built the way the essay described:
<div class="grid">
<div class="grid-row">
Q3 Report <button popovertarget="menu-1">⋮</button>
</div>
<menu id="menu-1" popover>
<button>Rename</button><button>Archive</button><button>Delete</button>
</menu>
<div class="grid-row">
Q4 Report <button popovertarget="menu-2">⋮</button>
</div>
<menu id="menu-2" popover>
<button>Rename</button><button>Archive</button><button>Delete</button>
</menu>
<div class="grid-row">
Annual Summary <button popovertarget="menu-3">⋮</button>
</div>
<menu id="menu-3" popover>
<button>Rename</button><button>Archive</button><button>Delete</button>
</menu>
<div class="grid-row">
Budget Draft <button popovertarget="menu-4">⋮</button>
</div>
<menu id="menu-4" popover>
<button>Rename</button><button>Archive</button><button>Delete</button>
</menu>
</div>
.grid-row {
anchor-name: --row-anchor;
}
[popover] {
position-anchor: --row-anchor;
position-area: right;
}
Click "Q3 Report" 's ⋮. The menu opens — popovertarget handles that part correctly no matter what — and positions itself against "Budget Draft," the last row in the grid. Click "Annual Summary" 's ⋮ instead. Same position. Click "Q4 Report" 's. Same position again. Every menu, from every row, lands in exactly the same place: wherever the last .grid-row happens to sit.
Why Nothing Stopped It
Each <menu popover> sits directly after its own .grid-row, as a sibling, both children of .grid. Neither one contains the other. When the browser resolves position-anchor: --row-anchor for any of the four menus, it looks for the nearest ancestor carrying that name first, and finds none, because no menu is nested inside any row. With no ancestor to prefer, resolution falls through to the only rule left standing: the last element in the document with that anchor name. All four rows share the identical name, set through the same class every row already uses for its layout. "Budget Draft" wins every time, for every menu, regardless of which button was actually clicked.
The Fix
Nothing about the anchor name changes, and nothing about the button. Each row and its own menu get wrapped together, and the anchor name gets scoped to that wrapper:
<div class="grid">
<div class="row-wrapper">
<div class="grid-row">
Q3 Report <button popovertarget="menu-1">⋮</button>
</div>
<menu id="menu-1" popover>
<button>Rename</button><button>Archive</button><button>Delete</button>
</menu>
</div>
<div class="row-wrapper">
<div class="grid-row">
Q4 Report <button popovertarget="menu-2">⋮</button>
</div>
<menu id="menu-2" popover>
<button>Rename</button><button>Archive</button><button>Delete</button>
</menu>
</div>
<!-- Annual Summary and Budget Draft, same pattern -->
</div>
.grid-row {
anchor-name: --row-anchor;
}
.row-wrapper {
anchor-scope: --row-anchor;
}
[popover] {
position-anchor: --row-anchor;
position-area: right;
}
.row-wrapper is now the common ancestor holding both a .grid-row and its own menu, and nothing else's. Scoping --row-anchor to it means each menu can only resolve the name within its own wrapper's subtree. Every other row's identically-named anchor stops existing as far as that menu is concerned.
Click "Q3 Report" 's ⋮ again. It positions against "Q3 Report." Click "Annual Summary" 's. It positions against "Annual Summary." The fix isn't specific to whichever row got tested first — every wrapper carries the same scoping rule, so every row gets the same correct result, independently.
Whether the Claim Held
The essay's claim was that implicit anchoring already handles the ordinary case for free, and the real cost shows up only where a requirement steps outside it while old structural habits go un-examined. Both halves held. Anchoring to the row instead of the button was never the mistake — that was a real requirement implicit anchoring genuinely can't serve. The mistake nobody made on purpose was leaving the menu where JavaScript-era overlays always used to live: next to its trigger, not inside anything, because that used to be the only way to keep it from being clipped. The browser didn't fail to find an ancestor. There wasn't one to find.
Scoping didn't require touching the anchor name, the button, or the popover relationship. The one piece that had to change was exactly the piece nobody had reason to look at again until a grid with more than one row proved it wrong.
Reference Article:
Native Popovers Didn't Replace JS Libraries — They Replaced Glue Code
Top comments (0)