Three important things you should know about CSS :is()

Back in 2019 I shared how the CSS :is() selector will simplify things when writing CSS. What I didn’t know back then, and only have learnt quite recently, are these three important facts about CSS :is():

  1. The selector list of :is() is forgiving
  2. The specificity of :is() is that of its most specific argument
  3. :is() does not work with pseudo-element selectors (for now)

Let’s take look at what that means.


# 1. The selector list of :is() is forgiving

What if you include a selector that’s pure gibberish inside :is()? Will the rule-set be declared invalid or what?

p:is(.foo, #bar, $css:rocks) {
  color: hotpink;

Thankfully :is() is very forgiving here: the $css:rocks part — which in itself is an invalid CSS selector — will simply be ignored, while keeping the rest of the selector list in place.. So using the snippet above, both and p#bar will be colored hotpink. Yay!

Should you try this without :is(), the whole rule-set would become invalid. In the snippet below, none of the paragraphs will be hotpink due to that faulty $css:rocks selector invalidating the whole selector list.

p {
  font-family: sans-serif;
}, p#bar, p$css:rocks { /* ❌ This whole rule-set is declared invalid */
  color: hotpink;

Note that the paragraphs will have font-family: sans-serif applied, as it’s only the invalid rule-set that ends up being ignored.

🔮 In the near future this latter behavior will no longer be the case as the CSSWG intends to modify these rules such that an invalid selector will simply be ignored rather than invalidating the whole selector list. Relevant CSS WG Issue: 3264


# 2. The specificity of :is() is that of its most specific argument

Take the code below. What color will have?

p:is(.foo, #bar, $this:invalid) {
  color: hotpink;
} {
  color: lime;

I won’t be lime but hotpink! This because when calculating the specificity, the specificity of the :is() pseudo-class is replaced by the specificity of its most specific argument.

  • has a specificity of (0,1,1)
  • p:is(.foo, #bar) has a specificity of (1,0,1)

As p:is(.foo, #bar) has a higher specificity, it will “win” here.

☝️ The :not() and :has() pseudo-classes also have their specificity calculated this way.

☝️ If you don’t want to be affected by this, you can use :where() instead of :is(). It works in the same way :is() does, but will always have a specificity of 0. You can cleverly wrap this around other selectors to undo their specificity. Think of :where(:not(…)) for example.

😬 Although I wouldn’t recommend it, you could perfectly do something like :is(#bump#up#the#spe#ci#fi#city#yo, .foo) to override selectors more specific than .foo


# 3. :is() does not work with pseudo-element selectors (for now)

If you read up on the definition of :is() you’ll read that it accepts a “Selector List” which is a comma-separated list of simple, compound, or complex selectors.

When looking up simple selectors, there’s an interesting thing to note:

A type selector, universal selector, attribute selector, class selector, ID selector, or pseudo-class is a simple selector.

Do you see it? Here: pseudo-element selectors are not included in this list. As a result, :is() does not play nice with pseudo-element selectors such as ::before, ::after, ….

🔮 In the future this will become possible though, but not just yet. Relevant CSSWG Issue: 2284


Knowing these three facts about :is() will surely help you understand it better and make using it more fun!

See the Pen
The CSS :is() pseudo-class. What color will .foo have?
by Bramus (@bramus)
on CodePen.

If you understood well, the Pen above should hold no secrets to you anymore 🙂


Did this help you out? Like what you see?
Thank me with a coffee.

I don't do this for profit but a small one-time donation would surely put a smile on my face. Thanks!

☕️ Buy me a Coffee (€3)

To stay in the loop you can follow @bramus or follow @bramusblog on Twitter.

“On the origin of cascades“, a talk by @hdv on how CSS came to be

Hidde recently gave a talk at CSS Café on the origins of CSS:

It’s been 25 years since the first people proposed a language to style the web. Since the late nineties, CSS lived through years of platform evolution. The cascade, specificity and the enormous choice in values and units set the language up for success. But not everyone liked to use these features everywhere. Some began to adapt the language to meet their needs.

In this Darwin-themed talk, you’ll learn how CSS came to be, and how the language’s simplicity and flexibility still make it stand out today.

In addition to the video embedded at the top of this post you can also check out the slides or read a full transcript.

👨‍🎓 Looking for more lessons in CSS History? The Languages Which Almost Became CSS by Zach Bloom also touches the same subject.

The CSS Cascade – Or, How browsers resolve competing CSS styles

Amelia Wattenberger is at it again with this nice interactive page on the CSS Cascade

Every time we write a CSS declaration (or rule), it will enter the CSS Cascade, which will determine whether or not it will end up as the final style. The further down the cascade a declaration falls, the less likely it will end up as the final style.

Time to get your specificity mojo on! But don’t be fooled, there’s more to only element/class/id differences …

The CSS Cascade →

The Specificity Graph

The Specificity Graph is a very simple model for diagrammatically assessing the overall health of your codebase in terms of specificity—a way of looking at an entire project’s CSS and highlighting any potentially troublesome areas of higher-than-ideal specificity. We can then use this snapshot to refactor and rearchitect old projects into a better shape, or to ensure we’re writing new projects in a sustainable manner.


A graph with a lot of peaks and troughs is a bad Specificity Graph: it is telling us that our CSS is full of—or prone to—specificity issues because of weighty selectors being defined before lighter ones.

After refactoring, a specificity graph should ideally look like so:


When using SMACCS, a graph would ideally look like this:


The Specificity Graph →
Specificity Graphs vs. SMACCS →