v149 · css · behavior change
User-Action Pseudo-Class Top-Layer Boundary
Chrome 149 implements the CSS Selectors Level 4 rule that :hover, :active, and :focus-within propagate up the ancestor chain only as far as the nearest top-layer element — so interacting with a popover or dialog no longer accidentally triggers hover/active styles on elements behind it in the regular document flow.
:hover or :focus-within propagating from a popover or dialog into its DOM ancestors, those rules will stop matching in Chrome 149. This is the correct interoperable behaviour per the spec.
at a glance
| Shipped in | Chrome 149 / Edge 149 |
|---|---|
| Status | Enabled by default |
| Flag | None |
| Spec | CSS Selectors Level 4 — user-action pseudo-class propagation rules |
| ChromeStatus | 6296574159355904 — User action pseudo class top layer boundary |
Browser-status disagreement, both sides reported rather than resolved (fetched 2026-10-07): this page says "Enabled by default" and lists "Chrome 149 / Edge 149" under "Shipped in", while the ChromeStatus feature 6296574159355904 reports status "Proposed" with desktop, Android, and WebView milestone 149. Neither side is adjudicated here.
what changed and why
The :hover, :active, and :focus-within pseudo-classes propagate upward through the DOM: when an element matches, all its ancestors match too. This makes sense for regular document flow — hovering a button inside a card should allow you to style the card.
Top-layer elements (popovers, <dialog>s, and fullscreen elements) are special: although they exist in the DOM tree, they are rendered in a separate stacking layer entirely above the rest of the page. When a button inside a popover is hovered, propagating :hover all the way up to the <body> or a container <section> makes no visual sense — those elements are displayed behind the popover and the user is not interacting with them.
CSS Selectors Level 4 specifies that user-action pseudo-class propagation stops at the first top-layer element in the ancestor chain. Chrome 149 implements this rule.
Source: blink-dev Web-Facing Change PSA.Selector-ancestry disagreement, both sides reported rather than resolved (fetched 2026-10-07): this page says the pseudo-classes "propagate upward through the DOM" so that "all its ancestors match too", while the CSS Selectors Level 4 specifies that they also match on the element's flat tree ancestors, up to and including the first top layer element or the root element, whichever is encountered first. Neither side is adjudicated here.
the rule
Given this DOM structure:
<main>
<div popover id="panel">
<button>Click me</button>
</div>
</main>
When the user hovers the <button>:
| Element | Before Chrome 149 | Chrome 149+ |
|---|---|---|
<button> |
:hover matches | :hover matches |
<div popover> (top-layer element) |
:hover matches | :hover matches |
<main> (ancestor of the top-layer element) |
:hover matches | :hover does NOT match |
<body>, <html> |
:hover matches | :hover does NOT match |
The same boundary rule applies to :active and :focus-within. It applies to all top-layer promotion types: popover attribute, <dialog> (opened with showModal()), and requestFullscreen().
example
CSS that breaks in Chrome 149
/* This selector will NO longer match when the user hovers a button
inside a popover that is a descendant of .sidebar. */
.sidebar:hover {
background: var(--hover-tint);
}
CSS that still works
/* Hovering within the popover itself still sets :hover on the popover. */
[popover]:hover {
box-shadow: 0 4px 20px rgba(0,0,0,.2);
}
/* Hover on regular (non-top-layer) ancestors still propagates. */
.card:has(button:hover) {
border-color: var(--accent);
}
Feature detecting the new behaviour
// No CSS feature query for this change. Use the :has() workaround
// for styling ancestors of hovered children, which works regardless.
.container:has(:popover-open:hover) {
/* applies when an open popover inside .container is hovered */
}
affected pseudo-classes
:hover | Stops propagating at the first top-layer ancestor. |
|---|---|
:active | Stops propagating at the first top-layer ancestor. |
:focus-within | Stops propagating at the first top-layer ancestor. |
:focus, :focus-visible | Not affected — these match the focused element itself, not its ancestors. |
browser support
| Chrome | 149 |
|---|---|
| Edge | 149 (Chromium) |
| Firefox | Status unknown (as of Chrome 149) |
| Safari | Status unknown (as of Chrome 149) |