Simplifying redundant selectors
If you’ve got redundant CSS selectors like this:
/* 😕 */
.active a,
.active button,
.active label {
color: steelblue;
}
Did you know that you can rewrite it like this?
/* 🤩 */
.active :is(a, button, label) {
color: steelblue;
}
That’s right, the :is() pseudo-class is built into plain-ole CSS now.
You can use :is()
to group any part of a selector, for instance, you could similarly transform this:
.section h2,
.aside h2,
.nav h2 {
color: steelblue;
}
into just this:
:is(.section, .aside, .nav) h2 {
color: steelblue;
}
But :is()
is not just useful for parents and children - it can also select multiple adjoining selectors as well, like:
button:is(:focus, :hover, :active) {
color: steelblue;
}
button:is(.active, .pressed) {
color: lightsteelblue;
}
Which behave equivalent to:
button:focus, button:hover, button:active {
color: steelblue;
}
button.active, button.pressed {
color: lightsteelblue;
}
Comparison to :where()
:where()
is a very similar pseudo-class to :is()
and is worth being aware of as well. They look very similar:
:where(.section, .aside, .nav) h2 {
color: steelblue;
}
But the difference is that :where
has a specificity of 0, whereas :is()
always takes on the specificity of the most specific selector in the list.
So, do you know what color the button will be in this CSS?
:is(html) button {
color: red;
}
:where(html) button {
color: blue;
}
In the above example, although the block starting with :where()
is below the block starting with :is()
, the :is()
block has a higher specificity (1 for the html
tag, + 1 for the button
), vs the block below it that has a lesser specificity (0 for html
because it is within :where
, and +1 for button
).
Comparison to :has()
A related, but very different, pseudo-class is :has()
. :has()
allows you to select a parent that contains a child matching a selector (or set of selectors).
An example use case of :has()
is to not underline links that contain an image or video:
a { text-decoration: underline }
/* Links are underlined, unless they contain an image or video */
a:has(img, video) {
text-decoration: none;
}
Now, if by default our a
tags have underlined text, but we have an image or video inside one, the underlining will be removed for any matching anchor elements.
You can also combine this with :is()
:is(a, button):has(img, video) {
text-decoration: none;
}**Note though that :has() is [*not* yet supported in all major browsers](https://developer.mozilla.org/en-US/docs/Web/CSS/:has#browser_compatibility), so use with caution.**
Do we still need preprocessors?
Now you may be reading this and saying “SCSS can do this!”, and you may even prefer its syntax:
.active {
button, label, a {
color: steelblue;
}
}
And you would be right, this is quite elegant. But it seems like every day CSS natively gets features that we once needed SCSS (or other preprocessors) for.
CSS variables has also been another incredible addition to CSS itself, that it begs the question when or how often you really need preprocessors anymore:
/* Plain ole modern CSS baby */
.active :is(a, button, label) {
--color: steelblue;
color: var(--steelblue);
}
That’s not to say preprocessors don’t still have their use cases and merits.
But I think at one point in time they were truly mandatory for handling any non-trivial CSS (elegantly, at least), and now that’s not as much the case anymore.
One last surprise…
Just to leave things on a high note, I want to point out that the future of CSS continues to look bright. The CSS Working Group is actively working on adding nested selectors directly to CSS. They are actively deciding between 3 possible syntaxes (known as options “3, “4”, and “5”):
/* Option 3 */
article {
font-family: avenir;
& aside {
font-size: 1rem;
}
}
/* Option 4 */
article {
font-family: avenir;
} {
aside {
font-size: 1rem;
}
}
/* Option 5 */
@nest article {
& {
font-family: avenir;
}
aside {
font-size: 1rem;
}
}
Which do you like best? And does your choice match the winner in the official poll?
Spoiler alert - #3 won the poll, so we may be getting a very SCSS-like nesting syntax as an icing on the cake to CSS soon, assuming the CSSWG’s chooses the poll winner.
And yes, I agree, Option 4 is an absolute abomination that needs to burn in fire.
Browser support
Browser support for :is()
and :where()
The :is()
and :where()
pseudo-classes are supported in all major browsers:
Source: MDN
Browser support for :has()
Note that the :has()
pseudo-class that we touched on here does not have the same level of support, so use :has()
with caution:
Source: MDN
Conclusion
Modern CSS is awesome, and only keeps getting better. Next time you’ve got redundant selectors in your code - don’t forget to reach for that handy :is()
pseudo-class.
About me
Hi! I'm Steve, CEO of Builder.io.
We make a way to drag + drop with your components to create pages and other CMS content on your site or app, visually.
You can read more about how this can improve your workflow here.
You may find it interesting or useful:
Top comments (13)
This is very well written. Congrats!
Wonderful info! Thanks!
ugh wow, great article. Learned a lot here.
Thank you.
Nice summary for those out from CSS for some time. Thank you!!!!
Thank you for this.
There's always something new to learn about CSS.
I voted for nested option 3. But option 4 has grown on me. When I first saw option 4, I thought it was an abomination too, but the more I've thought about it, the more elegant a solution it seems to be. It solves the problem of telling selectors and declarations apart at a clear syntactic level, whereas option 3 requires a non-obvious "trick" for some cases.
Also, option 4 makes it much clearer - this is CSS nesting, not SASS nesting. The way in which selectors nest is significantly different in CSS to the macro-expansion approach of SASS and there's some merit in being able to view a nested rule in isolation and be able to see which type of nesting the author intended.
Is the example written here correct? The brackets don't make any sense compared to...well, pretty much any other language. It seems like there's 2 to many.
Yes, they are correct. The idea is the structure syntax for a rule would be
selector { declarations } { nested rules }
where each nested rule would follow the same pattern. The
{ nested rules }
part would be optional, so that existing CSS rules would still be compliant.There was an early proposal for using
selector { declarations { nested rules } }
but the community voted it out on the (somewhat doubtful in my opinion) grounds that it would result in too rapid indentation of nested rules.
Ahhh, I see it now once laid out like that. You're right, that's a pretty elegant solution. I do still prefer option 3, but that could be because of the existing familiarity with it. I guess option 3 also potentially could mean you could pull out the sass build step if all you were using it for was nesting.
One thing I always wondered about with :is():
What color is the
example link
:I think it should be red because #pushme makes the specificity of 1: (1,1,0) compared to (0, 1, 1) for case 2 right? :is() is not the specificity of the matching selector. IIUC all of :is() gets boiled down in the most specific of its selectors (max(selectors)) and not (sum(selectors)).
Would using selectors that have the same base specificity make overiding without
!important
easier/possible be a best practice?This is probably where :where() (hmm, pun not intended) could be useful to allow overriding the casade later in the file.
I've considered using
:is(:where(a), #FOO.FOO:not(*))
as a specificity hack. Using:not(*)
makes it clear that that entry in the selector list is just for setting the specificity since it can never accidentally match any element, and whatever precedes the:not(*)
sets the specificity value.But it's still a hack, and cannot be considered best practice any more than
!important
. You should instead be looking to set the selector to play nicely in the room with the other selectors of the page. The selectors should be chosen to describe the semantics of the elements they match, and if you do that, you should never need to hack a specificity.I'm really stuck in web dev roadmap, I learn html css very well. But when it comes to practice, i can't make a simple web page !!
That's making me feeling sad, i love CSS but i don't know to master it. and sometimes my mind telling me navigate to backeend maybee you'll find your passion thhhere knowing that i coming from low level ( C - C# - C++ - Perl ) but i said how can i become backend developer and i don't know to build a basic ui with html css its really shame !! please help guys