CSS stands for cascading stylesheets, which basically means things later on in the page take precedence over things earlier (with some major caveats). This also applies to how we select elements - from parent to child, with no way to select parents, until now.
In the CSS Selectors 4 specification, CSS introduces a new selector called :has()
, which finally lets us select parents. What that means is we'll be able to target a CSS element which has
specific children within it. This is already supported in Safari, and is also in Chrome 105. The full support table is shown below:
With support increasing, in this article I will focus on how CSS parent selection works, and how you can do it today where support is available. In the meantime, if you require full support in all browsers, you can also implement this polyfill until native CSS support is available.
How Parent Selectors work in CSS
In CSS, if we want to select something, we use selectors that descend the DOM. For example, selecting a p
tag within a div
tag looks like this:
div p {
color: red;
}
Until now, we couldn't really select the div
tags which had p
tags within them, though, and this meant we had to resort to Javascript. The main reason this wasn't implemented in CSS is because it's quite an expensive operation to do. CSS is relatively fast to parse, but selecting parent tags requires a relatively significant larger amount of processing.
Using the :has
selector, we can now select div
elements which have a p
children, or any normal combination of selectors. For example, selecting a div
with a child p
now looks like this:
/* Makes the div color: red; */
div:has(p) {
color: red;
}
This will make any div
with a child p
red.
Combining parent selection with other selectors
Just like any other CSS selector, we can combine this for specific circumstances. For example, if you want to select only div
tags which have direct span
children:
div:has(> span) {
color: red;
}
As the vocabulary of :has
suggests, it is not just limited to parent selection. For example, below we can select a span
which :has
a sibling div
:
span:has(+ div) {
color: red;
}
Or even, selecting an element which does not have a child, by using the :not()
selector. For example, the following will select any div which does not have a p
child:
div:not(:has(p)) {
color: red;
}
Selecting elements which only contain text in CSS
One very common problem in CSS is that the :empty
tag does not select elements which contain any text - so sometimes an element can contain one space, and :empty
will not apply. The :has
selector gives us the power to select elements which only contain text nodes, and no other child elements.
Although this is not the perfect solution for simply :empty
elements with spaces (as this will select any element with just text and no additional HTML DOM elements) - it does give us the ability to select DOM elements with only text nodes, which was not previously possible. We can achieve this with the following code:
div:not(:has(*)) {
background: green;
}
Conclusion
With the addition of :has()
selector support in Chrome 105, parent selection is quickly becoming a reality that we will soon be able to use on real life projects. As of now, with Safari support, it's easy to test and see how it will work in the future. This has the additional benefit of letting us cut back on Javascript solutions to parent selection which is quite common in many applications and products.
You can even start using :has
today, if you also implement this polyfill, and then remove the polyfill once native support comes to Chrome and other browsers.
I hope you've enjoyed this article. To learn more about CSS, click here.