:Has Selector

Exploring the New :has Pseudo Selector and Its Uses

Lawrence Chabela CSS & HTML, Design, Development Technologies Leave a Comment

Sooner or later we all run into that inevitable moment in our CSS, “If I could only select this parent by some child context.” And then, the cold truth settles in, we don’t have those superpowers yet.

Everyone who has ever written CSS has undoubtedly found themselves in the same dilemma, thinking, “Why don’t we have a parent selector in CSS?”

Through the years, we were told a parent selector would be an engineering feat that could not be achieved due to the way browsers render a page and apply computed styles to elements as a stream, one element after the other. When the browser paints a parent and inevitably its children, reevaluating this already-painted DOM to evaluate parents for children containing a particular context would be too expensive of a task.

As the years moved on, we all applied different hacks or strategies, either reworking our DOM to avoid the issue altogether or using various JavaScript techniques to select parents and add CSS classes to use instead. We were just waiting for something to save us…

Our Hero Arrives

Over the last few months, browsers have started rolling out the new cool kid on the block of CSS selectors, the :has pseudo selector. This selector was added to the CSS Level 4 Selectors.

Straight from the spec, it says,

“The relational pseudo-class, :has(), is a functional pseudo-class taking a forgiving-relative-selector-list as an argument. It represents an element if any of the relative selectors would match at least one element when anchored against this element.”

Yep, clear as mud, right? Let’s try to break down what is being said above with a small example to get our feet wet with the :has selector. Assume you want to select all card elements that include an img element, to do so we can write the following:

.card:has(img) {
    ...
}

As you can see, we are selecting all of the .card elements that contain an img tag. Simple and powerful, right? Let’s dive a little deeper into our :has selectors powers.

Not Just About The Parents

This is not just about determining whether a parent has a child; we can also determine whether an element is followed by some other particular element of interest. For example, take the following.

.card:has(img + p) {
    ...
}

Our example from above takes our first example and spices it up a bit. The selector now is selecting every .card that has an img within it but also has a direct sibling of a p.

Now, to keep building on what we have learned, the above selector can actually be inverted to select any .card that does not have an img within it.

.card:not(:has(img)) {
    ...
}

Using the :not selector we can then nest the :has() selector to define the context we don’t want our selected element to have.

We can go further with this idea by also selecting elements by the context of some behavior within them. Take, for example, this:

form:has(:checked) {
…;
}

The above now allows us to apply styles to a form any time it has a :checked input in it. Layering more onto this selector, we can also play around with showing optional form fields:

.additional-field {
display: none;
}

form:has([value="someValue"]:checked) .additional-field {
display: block;
}

With our selector superpowers, we can now check if our form has an input with a selected value of someValue. When we do have the selected value, then we can show our .additional-field by switching its display property to block.

Isn’t that pretty cool? Something that normally all of us would have reached for our Javascript tricks to do can now be done with a few lines of CSS. Nice!

Using :has with Quantities

Using the :has selector, we can start to think of more creative solutions to the various layouts we define in our CSS. One scenario we can work through is the idea of changing our layout/styles based on child count.

When styling normally, our styles are written from the point of view of a top-down approach. Meaning, our styles are applied to our component without the context of what’s inside our component. What would be nice is the other way around where we can style based on what our content is.

With the combination of the :has selector and a technique called quantity queries, we can achieve styling based on the number of children a component contains.

Take for example a header component with a title and an icon:

<header class="header">
<span class="icon"></span>
<h1 class="title"></h1>
<header></header>
</header>

Now, let’s set up our styles to always have our title and icon on the left of our header.

.header {
display: flex;
justify-content: start;
}

So now, when our header is displayed, it will display its icon and title aligned to the start of our header’s x-axis. But, let’s say we optionally have a third element in our header, maybe an action of some sort.

With most three-element layouts in a header, we tend to space our actions on a separate side from other content. Now, how might we define our styles to align this optional item on the other end of our header’s x-axis?

Using the :has selector, we can target our header when it has a particular number of children within it. To do so would look like this:

.header {
display: flex;
justify-content: start;
}

.header:has(:nth-child(n + 3)) :nth-child(3) {
margin-left: auto;
}

Here we are evaluating if the .header has three or more children by using a quantity query that targets a container that has three or more children. Using this allows our :has selector to target the .header when it has three or more children.

When we do have three or more children, we can apply a left margin to our third child to force all children after it to the end of the .header. Now we have a layout that adapts to its content bottom-up instead of top-down.

Support For :has

Support for the :has selector as of writing this article is actually pretty widely available in most modern browsers.

:has pseudo-class selector

Even with the wide support of the :has selector in most evergreen browsers, it’s still a good practice to apply newer features as a progressive enhancement. To do so, use the @supports feature query using the selector().

@supports selector(:has(*)) {
…;
}

This way, we can target browsers that support the :has selector and add on our styles that are dependent on browsers that support the :has selector.

In Conclusion

The possibilities of using :has selector in useful and creative ways to write more flexible and expressive CSS seems endless. With so many of the new features that are currently being added to CSS, I can’t wait to see what other creative ways the :has selector can used for or in combination with.

If you have any ideas for using the :has selector, drop me a comment below! And if you liked this post, take a look at the countless others on the Keyhole Dev Blog.

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments