Selecting parent elements has become easier

unwrapping the :has selector in CSS

Oct 28th, 2022

CSS

CSS has a lot of ways to select elements in HTML and so many options can make it difficult to choose which method is most sufficient. This becomes very apparent when we have multiple elements on the pages that share styles but one of them, in particular, has a different style than the rest.

My parent card does not have a border colour red because this card does not have an element with the subTitle class.

SubTitle

My parent card has a border colour of red because it has an element with the class of subTitle.

The traditional way to solve this problem is to place another CSS class on the component that is going to get this Special style. It may look something like this

<div class="card">...Some content goes here</div>
<div class="card special-card">
  ...Some content goes here
  <p class="card__subTitle">Some more content for the special class</p>
</div>
<div class="card">...Some content goes here</div>

The middle div element has two CSS classes: 1) one denoting that it will get all the styles in the card class and 2) denoting that it will get all the styles from the special-card class.

There hasn't really been a way to select an element with CSS if it has certain descendants -- at least not until recently.

The HAS Selector

The :has selector also comically known as the parent selector is a pseudo-class that was introduced in the CSS spec. :has allows for the selection of an element if the arguments passed into :has match at least one element when anchored against the parent element. Essentially it allows for the selection of an element based on the presence or absence of descendant elements.

Revisiting our example above, let's say our special card had a paragraph that implemend its own padding in the in-line direction. Since we want all the cards to have the same amount of padding we need some way of addressing this problem. We can reach for the solution above, or we can implement the :has selector. We can select the card if it has a paragraph as its child and then set the in-line padding on the card to 0. It looks something like this:

.card:has(.card__subTitle) {
  padding-inline: 0;
}

The above code block translates to if an element with the class of .card has a descendant with the class of .card__subTitle then set the in-line padding on the parent element (in this case the element with the class of .card) to 0.

Pop quiz!!!

<div class="card">...Some content goes here</div>
<div class="card special-card">
  ...Some content goes here
  <p class="Title">Some more content for the special class</p>
</div>
<div class="card">...Some content goes here</div>

Question: Looking at the code example above, which of the following correctly selects the parent element that has a child with the class of title?

Multiple selectors inside has

The cool thing is that we aren't just limited to putting one item inside the :has pseudo-class. We can chain on selectors and get really specific as to when we want this parent element to be selected. Chaining on selectors looks something like this:

.card:has(.card__subTitle, .Title) {
  padding-inline: 0;
}

The above code block translates to if an element with the class of .card has a descendant with the class of .card__subTitle or .Title then set the in-line padding on the parent element (in this case the element with the class of .card) to 0.

You might have noticed the keyword OR above. This is important and a key differentiator between :has and other selectors. Usually with CSS if you are chaining selectors, if one of the selectors in your chain does not match, then the entire rule is skipped. This is not the case with the :has pseudo-class, and it is something that differentiates it from other selectors. As long as there is one matching selector inside the :has pseudo-class, then the parent element will be selected.

You might be wondering how we may go about saying AND with this selector. More specifically how do we go about saying the following: Select the element with the .card class if its descendants have elements with the .first and .secondclasses. Using the scenario above .card:has(.first .second) matches at least one. What happens if we require both to be present?

We can attach another :has pseudo-class. It would look something like this:

.card:has(.first):has(.second) {
  padding-inline: 0;
}

The above code block translates to if an element with the class of .card has a descendant with the class of .first and .second then set the in-line padding on the parent element (in this case the element with the class of .card) to 0.

The other cool thing about :has is that we can chain complex selectors when the parent element is to be selected. We can say something like this:

.card:has(> .first .title) {
  /* Some css rule */
}

Only select an element with a .card class if it has direct decedents with a class of .first which has a descendent with a class of .title.

More than just a parent Selector

The :has pseudo-class is more than just a parent selector because we can use it to select other elements that are in relation to the parent element. The following describes how we are using the :has pseudo-class to select a paragraph element.

.card:has(.subTitle) > p {
  /*Some Css Rule */
}

The above code block translates to: select any paragraph element that is a direct decedent of .card if the .card has a descendant element with the .subTitle class. Putting this into practice, below we can see that the manager's name is in bold while the staff's name is not. This is being done with a rule similar to the one we described above.

Manager

John Doe

Staff

Brandon Smith

Specificity

Calculating specificity with the :has pseudo-class gets a bit complicated and so we are going to walk through an explanation and then some examples.

Let's say we have the following scenario:

.card:has(.first p) {
  /* Some css rule */
}

What is the specificity here? We first look at the selector outside the :has pseudo-class and then focus on the selectors inside the :has pseudo-class. Here we have 1 class selector outside :has and then 1 class selector & 1 element selector inside :has. Therefore, we say that the specificity of the entire selector is 2 class selectors & 1 element Selector.

Question: Calculate the overall specificity on the following selector: .Card:has(.first #second)

The interesting thing is that calculating specificity changes when there are multiple selectors inside the :has pseudo-class that are separated by a comma. In this case :has will only use the specificity of the most specific selector. This is demonstrated below:

.card:has(.first, p) {
  /* Some css rule */
}

What is the specificity here? Remember, we first look at the selector outside the :has pseudo-class and then focus on the selectors inside the :has pseudo-class. Here we have 1 class selector outside :has & 1 class selector & 1 element selector inside :has. Since the class selector and the ** element selector** are separated by a comma, we look at the selector with the highest specificity. In this case, it would be the class selector. Therefore, we say that the overall specificity is 2 class selectors.

In the case where we have multiple :has pseudo-classes, we calculate the specificity for the individual :has and then combine them all to get an overall specificity. The following is an example of this:

.card:has(.first):has(.second p) {
  padding-inline: 0;
}

Here we have 1 class selector outside :has, 1 class selector inside the first :has, and 1 class selector & 1 element selector inside the second :has. These specificities are all combined to give us an overall specificity of 3 classes and 1 element.

Question: Calculate the overall specificity on the following selector: .card:has(.first):has(.second, #third)

Browser support

According to Can I use as of the time of writing this article the :has pseudo-class is at 76% support in terms of browsers. Moreover, it is supported in the latest version of Chromium & Safari, and it is under an experimental flag in firefox (thus I expect it to be fully supported in firefox fairly soon).

Conclusion

This :has selector is an incredible selector and it gives us another way to author the CSS for our applications. I can't wait for it to be fully supported across all major browsers!

Alight I'll catch you in the next one -> peace out!

on this page

the has selectormultiple selectors inside hasmore than just a parent selectorspecificitybrowser supportconclusion

Last updated October 28th, 2022