Skip to main content Accessibility Feedback

:is() vs. :where()

Back in March, I wrote about the :is() pseudo-class and how it can be used to reduce selector complexity.

Today, I wanted to talk about another pseudo-class that does the same thing, :where(), how they’re different, and when to use one versus the other.

Let’s dig in!

How :where() works

You use the :where() pseudo-class exactly the same way you’d use :is(). Pass in a comma-separated list of selectors, and it groups them together.

For example, this…

.bg-primary-dark h1, 
.bg-primary-dark h2, 
.bg-primary-dark h3, 
.bg-primary-dark h4, 
.bg-primary-dark h5, 
.bg-primary-dark h6,
.bg-secondary h1, 
.bg-secondary h2, 
.bg-secondary h3, 
.bg-secondary h4, 
.bg-secondary h5, 
.bg-secondary-dark h6, 
.bg-secondary-dark h1, 
.bg-secondary-dark h2, 
.bg-secondary-dark h3, 
.bg-secondary-dark h4, 
.bg-secondary-dark h5, 
.bg-secondary-dark h6,
.bg-primary-dark a:not(.btn), 
.bg-secondary a:not(.btn), 
.bg-secondary-dark a:not(.btn) {
	color: #ffffff;
}

Becomes this…

:where(
	.bg-primary-dark, 
	.bg-secondary, 
	.bg-secondary-dark
) :where(
	h1, 
	h2, 
	h3, 
	h4, 
	h5, 
	h6, 
	a:not(.btn)
) {
	color: #ffffff;
}

What’s the difference between :is() and :where()?

The :is() pseudo-class takes on the highest specificity of the selectors in it, while :where() always has a specificity of 0.

For example…

If I pass them collectively into :is(), the specificity of all of the selectors would be 1.1.0, the highest specificity of the group.

/* specificity: 1.1.0 */
:is(h1, #main > .btn, .sandwich) {
	/* ... */
}

If I pass them collectivity into :where(), the specificity of all of the selectors would be 0.

/* specificity: 0 */
:where(h1, #main > .btn, .sandwich) {
	/* ... */
}

Should you use :is() or :where()?

There are no hard-and-fast rules, but a few useful guidelines…

  • If you want your selectors to specifically have the lowest specificity possible, use :where().
  • If all of the selectors you’re using have the same specificity and you want them to keep their natural specificity, use :is().
  • If you’re using mixed-specificity selectors and want them all to have the highest possible specificity, use :is().

A lot of unexpected side-effects can, in my opinion, be mitigated with through better CSS conventions.

Consider this example…

/* specificity: 1.1.0 */
:is(h1, #main > .btn, .sandwich) {
	/* ... */
}

That is, frankly, probably some badly written CSS.

That kind of combination of selectors and overrides implies that you might need to hoist your selectors up to a higher level, adjust how things are showing up in the cascade, or reach for some utility classes.