The CSS :has pseudo selector

The CSS :has pseudo-selector has been around for a while and is supported widely in the browsers

It's in the 2023 Baseline, which means it works accross Chrome, Edge, Safari and Firefox: https://caniuse.com/css-has.

The selector is often known as the parent-selector even though it can be used to a lot of other things. I'll dig into this in this post with some examples.

The basics

As mentioned above, the :has selector is often known as the parent-selector. So it's kind of a reversed version of the child combinator. It's not implemented this way, but it gives a great understanding of how it works:

.parent < .child {
	// styles for .parent when it contains .child
	/*
		This is not how it actually works, but gives an
		understanding of the concept if you're familiar
		with the child-combinator.
	*/
}

The way it's implemented in the browsers actually makes it a lot more powerful than if it was implemented as above. Let's take a look at how it works and what else you can do than target the parent:

.parent:has(.child) {
	// styles for .parent when it contains .child
}

.parent:has(.child) .another-child {
	// styles for .another-child when it's inside a .parent with a .child
}

.parent:has(:nth-child(8)) {
	// styles for .parent when it contains at least 8 children
}

.parent:has(.child:nth-child(8)) .child {
	// styles for all .child elements inside a .parent that has at least 8 .child elements
}

.parent:has(.child):has(.another-child):has(.third-child) {
	// styles for .parent when it contains .child, .another-child and .third-child
}

This way we can also target elements inside the container, when a specific element is present in the container. This gives us endless opportunities. But as with everything else - be careful not to overuse and overcomplicate things. You might end up using :has for everything 😉

Example 1 - the basics

Here's an example where we have 3 containers.

  • The first just with text inside.

  • The second with text and an image and a styled parent.

  • The third one also with text and image - but a styled p tag which occurs before the image in the code. This means that that the :has selector also can be used as a previous sibling selector.

We could easily obtain the same result with some CSS modifiers to the parent. If you're not using any CMS or other dynamic content, this could be a solution.

When working with dynamic content, you'd had to do some template logic to determine whether or not to put the modifier class on the parent. When we can obtain this with pure CSS, that would obviously be the way to go. And that's no matter if you're doing server-side or client-side code.

Considering the fact that we're working with layout, it also makes much more sense to just put the logic in the CSS.

Example 2 - change container layout based on it's content

Let's have a look at an example of a component with the ability to just have text, but also text with an image. We have 2 containers:

  • The first one is just with text.

  • The second one is with text and an image - we change the container layout to grid and place the header at the top left, text at the bottom left - and make the image span over the two rows with the headline and text, and place it to the right side.

As in the first example, we could easily do this with CSS modifiers. But let's drop the template logic and class population and just do it with pure CSS.

Example 3 - lock page scroll when modal is opened. And maintain scroll position when closing

This is a Vue-template where the modal element only get's rendered when we click the button. This way we can use the :has selector to style the body element if there's a modal open.

In this example we can scroll, open the modal (and lock the page scroll with pure CSS too), scroll inside the modal, close the modal - and still be at the same scroll position on the page itself.

Some years back you'd probably had to do some scroll-locking in JS, keep the scroll-position in a variable to be able to set the scroll-position when closing the modal - and add an extra CSS class to the body tag to obtain the same functionality. Now it's just a few lines of CSS.

Conclusion

The :has selector is a powerful, yet simple selector that can help us obtain layout changes and functionality that previously would require CSS modifiers (which can cause overpopulation of CSS class names in the DOM in extreme scenarios) and in some cases JavaScript.

This is just a few simple examples of how to use the selector. It also be used for:

  • Changing layout based on how many children a container has by combining the :has selector with the :nth-child(n) selector. Maybe you'd want a slider/swiper module when between 5-8 elements, and a grid view when over 8 elements.

  • Changing layout of a container by using multiple :has-sectors on the parent.

I'm sure you can come up with a lot more examples of how to use it in your projects. If you've got some great examples and want to share - please let me know at [email protected] and I'll update the post and credit you.