There’s a handful of really awesome new CSS Level 4 selectors on the horizon. You can read about them all in the most recent W3C Editor’s Draft. I’ll likely write about more of them in the future, but there’s one in particular that I’d like to focus on for this post:
:has(). All of the CSS Level 4 selectors do some pretty cool stuff, but
:has() strikes me as having the most exciting possibilities.
:has() is a functional pseudo-class, which means it uses parentheses to take an argument. The argument it takes is a list of selectors that are descendants of the element. Here’s an example:
Since the first
article has a
p element, it matches the selector. We could use any type of selector inside the parentheses. As long as our element contains at least one of them, then we get a match:
In essence this accomplishes what many consider to be the holy grail of missing CSS selectors: the parent selector.
More than Descendants
I previously stated that if our element contains one of the selector arguments, then we get a match. I phrased it that way for the sake of simplicity, but it’s not totally accurate. The selectors within the parentheses don’t necessarily need to be descendants of the element. They can be any selectors scoped by our element. To clarify what I mean by scope, think about this CSS:
Here we’re targeting the
span element, which is “scoped” by the
.main-header class selector but is not a descendant of it.
If we wanted to target
.main-header in a similar fashion, that’s where
:has() comes in:
Here we’re saying, “select all
.main-headers that have an adjacent sibling
span element.” And now the real power of
:has() begins to reveal itself. It’s as if we’re able to write our selectors in reverse, selecting the leftmost element instead of the rightmost.
A few more examples:
div:has(> input) - selects all
divs that are direct parents of
header:has(~ #my-id) - selects all
header elements that are general siblings of an element with an id of
.area:has(span, aside) - selects all elements with a class of
area that have either a
span or an
aside as descendants.
Getting Headscratchy with :not()
:not() is another useful CSS Level 4 selector that works exactly as you’d expect:
That selects all
h3 elements that don’t have
main-heading as a class name.
:has() can work together to achieve some interesting results:
This selects all
footer elements that don’t contain any
But note the difference between that example and this one:
Now we’re selecting all footer elements that contain something that isn’t a
div. It’s a subtle but important difference: in the first example the footer simply needed to not contain any
divs, but it didn’t necessarily need to contain anything. In the second example the footer must contain something, and that something mustn’t be a
As with many of the new CSS selectors, browser support for
:has() is currently non-existent. But that shouldn’t stop you from learning it now so that you’re in on the ground floor once it’s ready to use.