Hopp til hovedinnhold

So, you've heard talk of a so-called :has() selector, but don't really know what it is. Or maybe you just clicked the article hoping to learn something new. Well, this article is for you! Now that :has() is supported by almost all major browsers (except Firefox where you need a feature flag …), it is time to learn what it is, why you should care and how you use it.

What is it and why should I care?

:has() is a so-called pseudo-class—this makes it different from normal selection in CSS. Take a look at the example in figure 1. A pseudo-class works by styling the anchor (the thing before the colon) when the state behind the colon (the pseudo-class) is true. In figure 1, this means styling the div when it is hovered.

Image showing the folowing code: div:hovered, where it is explained that what precedes the colon, here the div, is called an anchor, while what follows the colon is called the pseudo-class, here hover
Figure 1–pseudo-class example

The same is true for :has(), it styles the element before : when the has() part is true. But when is has() true? When the relative selectors* in the parentheses are found**. This lets us style our anchor (e.g. the div in figure 1) based on other relative elements—like children.

* e.g. In div span, span is a relative selector to div
** pseudo-elements and :has() inside :has() is not supported

/* styles div when it has a span child */
div:has(span) {...}

“I don’t need a new pseudo-class for relative selection”, you might say, “div:hover p for example lets me style my paragraph element when my parent div is hovered.” This is true, but what if you want to style your parent div when the paragraph is hovered? div p:hover would just style your paragraph, not your div. With :has(), however, this is now possible! div:has(p:hover) will style the div element when it contains a p element being hovered! Let’s look at a few simple examples:

A parent selector

The most obvious use for :has() is as a parent selector. In the following example (see embedded CodeSandbox below) we have an .image-container div. This div contains an image and a .caption paragraph. The goal is to style our image-container background when it has a caption.

    <div class="image-container">
      <img src="https://thispersondoesnotexist.com/image" height="200px" />
      <p class="caption">
        This person does, unfortunately, not exist
      </p>
    </div>

Without using :has() we might try the following:

/* See result in CodePen example 1 below "Without :has()" */
.image-container .caption {
	background-color: lightblue;
}

This has the right idea, we check when an .image-container has a .caption within it and apply a background colour. Unfortunately, the background colour is applied to the .caption, not the .image-container. Let’s try with :has():

/* See result in CodePen example 1 below "With :has()" */
.image-container:has(.caption) {
	background-color: lightblue;
}

Now the style is applied to the .image-container instead of the .caption—just as we wanted! The styling is also removed when no .caption is present!

More than a parent selector!

Another usage for :has() is to style your element based on a sibling traits. A good usage for this is to change the spacing between adjacent headings.

<h1>This heading is good</h1>
<h2>This heading is gooder</h2>
<h3>This heading is the goodest</h3>
<p>Here be some fancy text</p>

With :has() we can remove this space for all adjacent headings like this:

:is(h1,h2,h3):has(+ :is(h2,h3,h4)) {
  margin-bottom: -1rem;
}

The :is() makes it work for all elements inside the parentheses and the + targets adjecent elements. The result is that h1, h2 and h3 elements followed by a h2, h3 or h4 element have a margin-bottom of -1rem.

Now use it!

And there you go. Now you know the :has() selector exist and you know how it works. The only thing left then is to go out and use it—good luck!

Did you like the post?

Feel free to share it with friends and colleagues