CSS :has() Can Select a Parent

CSS :has() lets a selector match an element based on what is inside or next to it. It is often described as a parent selector, although relative selectors make it useful for more than parents.

This selector matches an <a> only when it has a direct <img> child:

a:has(> img)

The > matters. Without it, an image anywhere inside the link would match.

Looking at the next sibling

:has() can also look forward in the document. This matches a <dt> immediately followed by another <dt>:

dt:has(+ dt)

That makes it possible to style the first element based on a sibling that comes after it—something ordinary CSS selectors could not previously do.

Finding sections without a heading

Combine :has() with :not() to find sections missing the expected heading elements:

section:not(:has(heading-display, heading-section, heading-card, heading-small, h5, h6))

The placement of :not() changes the meaning. This version is not equivalent:

section:has(:not(heading-display, heading-section, heading-card, heading-small, h5, h6))

It matches a section containing any descendant that is not one of those headings, which will be most non-empty sections. Parentheses are doing quite a lot of work here.

Browser support changed after publication

When this article was published in December 2021, Safari Technology Preview 137 had enabled :has() by default, Chromium was still implementing it, and Firefox did not support it. Browser support later improved, so check current compatibility before deciding whether a fallback is needed.

:has() removes JavaScript from several small conditional styling jobs. I would keep the selector focused, though; a readable parent rule is useful, while a long chain of nested conditions becomes difficult to understand surprisingly quickly.