Entry 06

:has() for parent and page state

Use it wherever a script adds a class to a parent because of what it contains or what state a child is in.

Open
The article with an image spans two columns. Only the heading before a paragraph loses its margin. The form lights up when you tab into its field. The page stops scrolling while the dialog is open, and does not shift sideways.
Code CSS · 6 lines
article:has(img) { grid-column: span 2; }
h2:has(+ p) { margin-block-end: 0; }
form:has(:focus-visible) { background: var(--surface-raised); }

html { scrollbar-gutter: stable; }
html:has(dialog:modal) { overflow: hidden; }
Why it works, the rules and the credits 2 reasons · 3 rules · support · credits

Why it works

  1. :has() matches an element by its descendants or following siblings, and it accepts state pseudo-classes. Content and form state can style any ancestor.
  2. On the root it works as a page-wide listener. The last rule locks page scroll while a modal dialog is open and lifts the lock when it closes, with nothing to clean up.

Rules

  1. :modal matches showModal() only, so a non-modal dialog does not lock the page.
  2. Keep scrollbar-gutter: stable with the scroll lock. Without it the page shifts sideways when the scrollbar disappears. The reset sets it.
  3. :has() cannot be nested inside :has().

Support

Chrome
105
Firefox
121
Safari
15.4

Credits

Borrowed from: Jen Simmons, Using :has() as a CSS Parent Selector and much more, 2022-08-18 · Josh W. Comeau, The Undeniable Utility Of CSS :has, 2024-09-09 · Chris Coyier, Scroll-Locked Dialogs, 2024-02-19