:has()

Jen Simmons wrote an excellent article about the new :has() CSS selector on webkit.org, titled Using :has() as a CSS Parent Selector and much more. As a quick recall, :has() is

… a way to apply CSS to an element based on what’s happening inside that element.
Jen Simmons

In other words, :has() is a parent selector. The parent selector can have a negative performance impact on the browser rendering[1]. Therefore it took an incredible 20 years of controversial discussions to finally come to a definition of the :has() pseudo-class in CSS Selectors Level 4, and subsequently to a browser implementation. Safari 15.4 supports it since March 14, 2022 and Chrome 105 will ship it on August 30, 2022. Browser support of :has() is still limited, which qualifies the use of it for learning and exploration scenarios but not for productive use, in my view.

Jen provides great examples in her article showing what can be done with the new selector and I agree to her statement, that The hardest part of :has() will be opening our minds to its possibilities. We’ve become so used to the limits imposed on us by not having a parent selector. Now, we have to break those habits.

A selector like h2+p would select the sibling paragraph directly coming hafter the h2 heading, which means styles can be applied to the p element – that´s not new.

h2+p {
margin-bottom: 0; /* set the bottom margin of p*/
}

With h2:has(+p) the h2 heading is selected which allows to style the parent heading based on what´s coming after – that is new. In this case you could reduce the margin of the heading if its following element is a paragraph, like

h2:has(+p) {
margin-bottom: 0; /* set the bottom margin of h2*/
}

While I personally think this kind of styling should be done with collapsing margins[2][3] it´s still a great example of how the new selector works.

Jen has more ideas, like styling form states without JavaScript through the use of pseudo-classes inside of :has(). I was not aware how many form-specific pseudo-classes are already available: :autofill, :enabled, :disabled, :read-only, :read-write, :placeholder-shown, :default, :checked, :indeterminate, :valid, :invalid, :in-range, :out-of-range, :required and :optional. E.g., to indicate invalid input fields and colorize their label red you could do:

input:invalid {
outline: 4px solid red;
border: 2px solid red;
}

div:has(input:invalid) label {
color: red;
}

That´s all exciting 😃.


  1. The other day I came across the below tweet by Antti Koivisto, showing a pretty quick running example of 320 :has rules and 25600 nodes. At least in the example given, performance doesn´t seem to be an issue.

    Here is WebKit trunk with 320 rules like “#container:has(.r123:hover) .tr123 { … }” and 25600 divs

    Image from Tweet
    ↩︎
  2. Josh Comeau: The Rules of Margin Collapse ↩︎

  3. Ire Aderinokun: What’s the Deal with Collapsible Margins? ↩︎