Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

CSS recently added the pseudo-classes :where() and :is() and I don't understand when to use which. Both can be used to select multiple elements. Here is a snippet where the same is achieved with both pseudo-classes:

span {
  display: inline-block;
  height: 100px;
  width: 100px;
  background-color: grey;
  margin-bottom: 10px;
}

:is(.span1-1, .span1-2):hover {
  background-color: firebrick;
}

:where(.span2-1, .span2-2):hover {
  background-color: teal;
}
<span class="span1-1"></span>
<span class="span1-2"></span>
<br>
<span class="span2-1"></span>
<span class="span2-2"></span>

Can someone give an example where they behave differently from each other?

like image 679
leonheess Avatar asked Dec 12 '25 11:12

leonheess


2 Answers

Explaining :is vs :where for those unfamiliar with CSS specificity:

Specificity is written as 3 numbers, and is of the form ID-CLASS-TYPE

For example:

#thing .my-class div has specificity 1-1-1

#thing .my-class div:hover has specificity 1-1-2 (pseudo classes are considered a type)

.my-class .my-other-class has specificity 0-2-0

#thing has specificity 1-0-0

div has specificity 0-0-1

To determine CSS rule priority, the specificities of rules are compared. Each of the 3 numbers are compared against other rules, letting the left-most comparison win.

For example, 0-2-0 wins against 0-1-3, and 1-0-0 wins against 0-2-0.

Now, to answer the question, :is is useful to avoid repetition, so that you can write the rule ul li, ol li as :is(ul, ol) li

Note that :is is not itself counted as a pseudo-class for the purposes of calculating the specificity of the rule.

:where lets you do exactly the same thing as :is, except the rules mentioned inside the :where() parentheses are not counted towards a rule's specificity calculation.

For example, :is(ul, ol) li has specificity 0-0-2, but :where(ul, ol) li has specificity 0-0-1.

You will only need to worry about your choice of where vs is if you find yourself in a situation where you need to consider the priorities of two similar CSS rules.

Note that any use of !important will take priority over any specificity comparisons.

like image 100
Andrew Parks Avatar answered Dec 15 '25 08:12

Andrew Parks


As mentioned, the difference is specificity. This is mentioned on MDN, though not prominently for some reason. The spec, on the other hand, is much more explicit about it:

4.4. The Specificity-adjustment Pseudo-class: :where()

The Specificity-adjustment pseudo-class, :where(), is a functional pseudo-class with the same syntax and functionality as :is(). Unlike :is(), neither the :where pseudo-class, nor any of its arguments contribute to the specificity of the selector—its specificity is always zero.

What are the use cases? Well, in the example you've given, it's not terribly useful. You don't have any competing selectors for which you need to match or otherwise not override specificity. You have a basic span rule, and a :hover rule that overrides it naturally (i.e. just by how specificity works and what specificity was originally designed for). If there aren't any special or exceptional styles you need to take into account, it doesn't really matter whether you use :is() or :where().

:where() becomes useful when you have more general rules that have unnecessarily specific selectors, and these need to be overridden by more specialized rules that have less specific selectors. Both MDN and the spec contain an example of a (very) common use case — I don't want to simply regurgitate what's on MDN, so here's the one from the spec:

Below is a common example where the specificity heuristic fails to match author expectations:

a:not(:hover) {
  text-decoration: none;
}

nav a {
  /* Has no effect */
  text-decoration: underline;
}

However, by using :where() the author can explicitly declare their intent:

a:where(:not(:hover)) {
  text-decoration: none;
}

nav a {
  /* Works now! */
  text-decoration: underline;
}

Unlike MDN, the spec doesn't really explain this use case in English, so I will. The "author expectations" here are that the nav a CSS rule (what I call a specialized rule) would override the a:not(:hover) rule (what I call a general rule). Ostensibly, this does not happen.

Because the :hover pseudo-class is more specific than type selectors, any rules that have only type selectors won't be able to override the general a:not(:hover) rule that applies to any a that isn't hovered. Traditionally you'd have needed to match the specificity of a:not(:hover), most naïvely by duplicating the offending bit:

a:not(:hover) {
  text-decoration: none;
}

nav a, nav a:not(:hover) {
  /* Works, but not ideal, to say the least */
  text-decoration: underline;
}

Alternatively, by adding selectors that increase specificity without affecting matching:

a:not(:hover) {
  text-decoration: none;
}

nav a:nth-child(n) {
  /* Works, but not ideal either */
  text-decoration: underline;
}

(or a:any-link, in the case of links)

What :where() does is allow you to remove the specificity added by :hover altogether, thereby making it much easier to override this rule for certain a elements, for example by ensuring that a elements in a nav are always underlined whether or not the cursor is over them (since nav a is more specific than just a).

Unusually, because it decreases a selector's specificity, you'd generally use :where() with selectors that need to be overridden, rather than the overriding selectors. On the other hand, you use :is() simply to reduce selector duplication in your CSS rules, e.g. by changing

.span1-1:hover, .span1-2:hover

to

:is(.span1-1, .span1-2):hover

while preserving specificity (though do note that :is() does work differently once you're grouping selectors with different specificity amounts).

This means that while :where() has a similar syntax to :is(), in practice they're two different pseudo-classes with different use cases. Their use cases do partially overlap nevertheless. For example, you may need to reduce selector duplication in a general rule, which implies using :is(), but you'd prefer :where() instead to also reduce specificity. Since it's useful to apply a single :where() to multiple selectors, allowing it to accept a selector-list makes it so you don't have to write :where(:is(selector-list)). This principle applies to many other new pseudo-classes such as :host(), :host-context() and :has(), as well as the level 4 :not().

like image 20
BoltClock Avatar answered Dec 15 '25 10:12

BoltClock



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!