← Chrome 149 reference

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.

Behaviour change If you have CSS rules that rely on :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 inChrome 149 / Edge 149
StatusEnabled by default
FlagNone
SpecCSS Selectors Level 4 — user-action pseudo-class propagation rules
ChromeStatus6296574159355904 — 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().

Source: CSS Selectors Level 4 and blink-dev PSA.

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

:hoverStops propagating at the first top-layer ancestor.
:activeStops propagating at the first top-layer ancestor.
:focus-withinStops propagating at the first top-layer ancestor.
:focus, :focus-visibleNot affected — these match the focused element itself, not its ancestors.

browser support

Chrome149
Edge149 (Chromium)
FirefoxStatus unknown (as of Chrome 149)
SafariStatus unknown (as of Chrome 149)
Source: chromestatus, May 2026.

see also