Here’s what I didn’t know about :where()
posted on
This is part 4 of my series Here’s what I didn’t know about… in which I try to learn new things about CSS. This time I'm trying to find out what I didn’t know about the :where()
pseudo-class.
Okay, I’ll be honest. When I heard about the :where()
pseudo-class for the first time, I wasn’t impressed because reading a rule like the following hurt my brain.
:where(header, main, footer) p:hover {
color: red;
}
It feels like using a shorthand property instead of longhand properties; fewer lines of code, but harder to process. I didn’t get the point, but a few days ago it clicked.
Here’s what I didn’t know about :where()
:
You can use it to lower the specificity of a selector
I believe it was on Andys blog where I saw this smart rule:
ul[class] {
margin: 0;
padding: 0;
list-style: none;
}
If a <ul>
has no class
, it’s probably a list in the true sense, so leave it untouched and display bullets. If the list has a class, it’s semantically still a list (in most browsers), but it’s likely that it doesn’t look like one, so reset the styling. Yeah, I know, dangerous assumptions, but it worked well for me in the past. The only issue I had with this solution is the high specificity caused by the combination of a tag and a class selector. If I wanted to change one of the default properties using just a class
selector, it wouldn’t work.
<ul class="space">
<li>HTML</li>
<li>CSS</li>
<li>JS</li>
</ul>
ul[class] {
margin: 0;
padding: 0;
list-style: none;
}
.space {
/* This doesn't apply because ul[class]
has higher specificity than .space */
margin: 2rem;
}
Open the first example on CodePen.
This is where :where()
comes into play. No matter which selectors you pass to the pseudo-class, :where()
will always have a specificity of 0.
ul:where([class]) {
margin: 0;
padding: 0;
list-style: none;
}
.space {
/* This applies because .space has higher
specificity than ul:where([class]) */
margin: 2rem;
}
Even though the selector contains a tag, a pseudo-class and an attribute, only the tag selector adds to the specificity.
Open the second example on CodePen.
Another great example is input styling (thanks for the tip, Christopher).
input:where([type="text"],
[type="email"],
[type="password"]) {
border: 2px solid #000;
}
[aria-invalid] {
border-color: red;
}
or if you're a fan of the :not()
selector:
input:where(
:not(
[type="button"],
[type="reset"],
[type="image"]
)
) {
border: 2px solid #000;
}
Open the third example on CodePen.
:where()
is a forgiving selector
If a list of selectors contains an invalid selector, none of the selectors will match.
<button>
Send
</button>
/* The background color won't change on :hover or :focus
because the invalid :touch pseudo-class makes the whole
list invalid */
button:focus,
button:hover,
button:touch {
background: red;
}
Open example 4 on CodePen.
If you use :where()
instead, :focus
and :hover
will still match.
button:where(:focus,
:hover,
:touch) {
background: red;
}
Open example 4 on CodePen.
Conclusion
I haven’t used :where()
in production yet, but considering that browsers support is pretty great, I’m going to try it out soon. The only concern I still have is the cognitive overload selectors like :where()
or :not()
create.