DEV Community

Cover image for Simpler CSS Selectors With :is()
Steve Sewell for Builder.io

Posted on • Updated on • Originally published at builder.io

Simpler CSS Selectors With :is()

Simplifying redundant selectors

If you’ve got redundant CSS selectors like this:

/* 😕 */
.active a,
.active button,
.active label {
  color: steelblue;
}
Enter fullscreen mode Exit fullscreen mode

Did you know that you can rewrite it like this?

/* 🤩 */
.active :is(a, button, label) {
  color: steelblue;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

into just this:

:is(.section, .aside, .nav) h2 {
  color: steelblue;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

Which behave equivalent to:

button:focus, button:hover, button:active {
  color: steelblue;
}

button.active, button.pressed {
  color: lightsteelblue;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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.**
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

Browser support table screenshot

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:

Browser support table screenshot

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:

Builder.io demo

Oldest comments (13)

Collapse
 
rouilj profile image
John P. Rouillard • Edited

One thing I always wondered about with :is():

What color is the example link:

<style>
.active :is(a, .highlight, #pushme) { color: red; } /* 1 */

.active a {color: blue;} /* 2 */
</style>

<a class="active">example link</a>
Enter fullscreen mode Exit fullscreen mode

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.

Collapse
 
alohci profile image
Nicholas Stimpson

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.

Collapse
 
po0q profile image
pO0q 🦄

This is very well written. Congrats!

Collapse
 
harmonygrams profile image
Harmony

Thank you for this.
There's always something new to learn about CSS.

Collapse
 
alohci profile image
Nicholas Stimpson

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.

Collapse
 
jamesthomson profile image
James Thomson

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.

Collapse
 
alohci profile image
Nicholas Stimpson • Edited

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.

Thread Thread
 
jamesthomson profile image
James Thomson

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.

Collapse
 
fininhors profile image
Francisco Junior

Thank you.

Collapse
 
jpcid profile image
Javier Pérez Cid

Nice summary for those out from CSS for some time. Thank you!!!!

Collapse
 
angelafox777 profile image
Angela Fox

Wonderful info! Thanks!

Collapse
 
fpigeonjr profile image
Frank Pigeon Jr. 🇵🇦🇺🇸

ugh wow, great article. Learned a lot here.

Collapse
 
cryptographer3301 profile image
user32dll

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