:has() The Parent Selector

Style an element based on what's inside it or what comes after it. The holy grail of CSS selectors.

CSS4 min readConcept 37 of 43

Selecting a parent based on its child

For over 20 years, CSS could only style an element based on its parents (e.g., .card img styles the image inside a card). There was no way to style the .card itself if it happened to contain an image.

The :has() pseudo-class solves this. It acts as a relational selector, letting you apply styles to an element if the selector passed inside the :has() parenthesis matches.

It is commonly called the 'parent selector', but it's even more powerful than that—it can look ahead at siblings too.

Replacing JavaScript class toggling

Before :has(), if you had a form group and wanted the label to turn red when the input inside it was invalid, you had to write JavaScript to listen for validation events and attach an .is-invalid class to the parent div.

With :has(), you can simply write .form-group:has(input:invalid) { color: red; }. The browser handles the state automatically, perfectly in sync, with zero JavaScript.

This drastically reduces frontend logic and keeps styling concerns entirely inside your CSS.

Descendants and Siblings

To style a parent based on a child, just pass the child selector into :has(). article:has(video) selects any article containing a video.

To style an element based on its state, combine pseudo-classes. label:has(input:checked) styles a label differently if its internal checkbox is checked.

You can also use combinators to look at siblings. h2:has(+ p) selects an h2 only if it is immediately followed by a <p> tag, letting you adjust margins dynamically.

Try it

Interact with the child elements below and watch how the parent container restyles itself instantly using purely CSS.

The :has() Relational Selector

Delete the @ symbol to trigger :invalid

// Target parent if child is invalid
.form-group:has(:invalid) {
border-color: red;
background: #fff1f2;
}
// Target sibling of invalid input
.form-group:has(:invalid) .error-msg {
display: block;
}
.form-group
valid@email.com
Please include an '@' symbol.

Check yourself

Pick an answer to lock it in, then read why. Getting one wrong is part of how it sticks.

  1. 1How do you select a `.card` only if it contains an `<img>` element?
  2. 2Which selector correctly targets an `<h1>` that is immediately followed by an `<h2>`?
  3. 3Why should you avoid writing `*:has(.active) { border: 1px solid red; }`?

Remember this

  • The :has() pseudo-class lets you select an element based on its descendants or subsequent siblings.
  • It eliminates the need for JavaScript when toggling parent classes based on child states (like :checked or :invalid).
  • You can use combinators inside :has(), such as :has(+ p) to look at the very next sibling.
  • Avoid using :has() with universal selectors (*) to maintain high rendering performance.

Done with this concept?

Mark it complete to track your progress. No login needed.