DEV Community

Diana Le
Diana Le

Posted on

Simplify CSS selectors with :is() and :where()

Introduction

The new pseudo-classes :is() and :where() help make compound selectors more efficient and less error-prone by allowing you to group and condense selectors, which makes them easier to maintain, but each has its own quirks with how it calculates specificity.

Keep in mind that while compound selectors in CSS are essential when learning about CSS and how the cascade works, in practice they are somewhat controversial. Sometime during the almost 10 years that I've been a web developer, they became frowned upon because they were difficult to maintain on large projects with multiple developers. Methodologies like BEM became popular in part because they avoided compound selectors entirely (and therefore removed the need to figure out CSS specificity on the fly). These pseudo-classes are still important to learn, however, since compound selectors are vital to CSS, and may also potentially be incorporated in other future CSS functionality, like nesting.

Let's walk through examples of how both of these pseudo-classes work. If you would like to test the CSS as you read, here is the HTML:

<header>
  <h1>About Us</h1>
  <h2>Meet our Team</h2>
  <ul>
    <li>Unordered List item one</li>
  </ul>
  <ol>
    <li>Ordered List item one</li>
  </ol>
</header>
<main>
  <article>
    <h3>Web Designer</h3>
    <p>This is our web designer.</p>
    <a href="#">View Bio</a>
  </article>
  <article class="card">
    <h3 id="heading">Web Developer</h3>
    <p>This is our web developer.</p>
    <a href="#">View Bio</a>
  </article>
</main>
<footer>
  <ul>
    <li>Unordered List item one</li>
  </ul>
  <ol>
    <li>Ordered List item one</li>
  </ol>
</footer>
Enter fullscreen mode Exit fullscreen mode

How Both :is() and :where() Work

With both :is() and :where(), you can group any selectors within the parentheses where you want to have the same declarations applied.

For instance:

h3,
p,
a {
   color: blue;
}
Enter fullscreen mode Exit fullscreen mode

Becomes:

:where(h3, p, a) {
   color: blue;
}
Enter fullscreen mode Exit fullscreen mode

The pseudo-classes become more powerful once you combine more complex selectors. For example, you can condense the following compound selectors:

Before:

.card h3,
.card p,
.card a {
   color: blue;
}
Enter fullscreen mode Exit fullscreen mode

After:

.card :is(h3, p, a) {
   color: blue;
}
Enter fullscreen mode Exit fullscreen mode

You can even chain them:

Before:

header ul,
header ol,
footer ul,
footer ol {
  color: green;
}
Enter fullscreen mode Exit fullscreen mode

After:

:where(header, footer) :where(ul, ol) {
  color: green;
}
Enter fullscreen mode Exit fullscreen mode

How These Help Minimize Errors

CSS is a declarative language; if the browser runs across a line that it doesn't understand, it skips it and keeps going. If we have the following CSS rule:

.card h3,
.card p {
   color: blurple;
   font-family: sans-serif;
}   
Enter fullscreen mode Exit fullscreen mode

The color blurple doesn't exist, and that declaration would be skipped, but then the font-family: sans-serif would still apply.

However if you had the following CSS:

.card h3,
.card p,
.card :second-child {
   color: blue;
   font-family: sans-serif;
}
Enter fullscreen mode Exit fullscreen mode

There is no such thing as a second-child selector, and this ENTIRE rule would fail silently, meaning neither color nor font-family will apply to any selectors even though both declarations are valid. The .card :second-child selector ruins the entire rule.

Forgiving Selectors

Here's where the other benefit to using these new pseudo-classes comes in. Each pseudo-class accepts a "forgiving selector list", which means each

"parses each selector in the list individually, simply ignoring ones that fail to parse, so the remaining selectors can still be used." - CSS Working Group

If you had instead written the previous code with either :is() or :where(), you would have a better outcome:

.card :where(h3, p, :second-child) {
   color: blue;
   font-family: sans-serif;
}
Enter fullscreen mode Exit fullscreen mode

Only the last selector :second-child fails due to the "forgiving selector list", so both .card h3 and .card p still get the CSS declarations of color and font-family applied.

Specificity: How :is() and :where() Differ

The major difference between :is() and :where() is how they handle specificity in regards to the selectors within the parentheses. Specificity determines which rules will get applied to selectors.

:where() has 0 Specificity

Any selectors using :where() will have 0 specificity. Put very simplistically, HTML tags have a specificity of 1, classes are 10, and IDs are 100. The calculations actually use 3 columns but this is just so you understand that almost all selectors have a specificity greater than 0. Generally you won't need to calculate exact specificity when writing CSS, but you can see that this means that anything inside :where(), will not apply if that specific selector has already been styled somewhere else beforehand.

/* specificity 0-0-1 */
article {
  color: blue;
}

/* This rule will not apply 
because it has lower specificity (0) 
than the rule above */
:where(article) {
  color: red;
}
Enter fullscreen mode Exit fullscreen mode

This makes :where() a good choice for CSS resets or any base styling that may need to be easily overridden later.

:is() Applies the Specificity of the Highest Selector

The pseudo-class :is() will take the highest specificity of whatever is in your selector list, and then apply that to all the selectors. This means for example that if you group a tag selector with an ID selector, the tag now has the same calculated specificity as the ID. You will not be able to override the styling for the tag afterwards unless you increase the specificity to be at least the same for a CSS ID.

article :is(p, a, #heading) {
  color: blue;
}

/* This rule will not apply
because the ID selector in the previous rule
gives the anchor tag a higher specificity than here */
article a {
  color: red;
}

/* Let's force the override with the dreaded IMPORTANT  */
article a {
  color: red !important;
}
Enter fullscreen mode Exit fullscreen mode

The specificity of is() makes it hard for me to think of a proper use case for it. If you are not careful with your selectors, such as mixing tags, classes, and IDs together, you will spend more time debugging why certain styles are not applying. If you make sure to only group similar selectors together (all having the same specificity), then this may be useful to use in lieu of the traditional compound selector syntax.

Top comments (2)

Collapse
 
reacthunter0324 profile image
React Hunter

Thank you
I learned :is() and :where() today

Collapse
 
dianale profile image
Diana Le

Great!