Some stylesheets repeat the same selector prefix over and over, line after line. :is() and :where() solve that problem by grouping several selectors into one, while behaving very differently on one crucial point: specificity.
Understanding that difference avoids hours of debugging when a rule unexpectedly overrides a style that seemed protected.
Definition
:is() and :where() accept a comma separated list of selectors and treat it as a single alternative. The rule that uses them applies as soon as one selector in the list matches the element.
Grouping repetitive selectors
/* without :is(), three repeated selectors */
header h1,
main h1,
footer h1 {
font-family: "Georgia", serif;
}
/* with :is(), a single grouped selector */
:is(header, main, footer) h1 {
font-family: "Georgia", serif;
}The visual result is strictly identical between the two versions, but the second one is shorter, easier to maintain, and removes the risk of forgetting to update one of the three selectors if the rule ever changes.
The specificity trap
This is where the two syntaxes diverge radically. The Specificity of :is() matches that of its strongest argument in the list: if the list contains an id, the whole rule inherits the weight of an id, even for the other selectors in the list that are simple classes. :where(), on the other hand, has a specificity of zero, no matter how complex the selectors it groups are. A rule written with :where() can therefore be overridden by any other rule, even the simplest one, which makes it a valuable tool for writing base styles that stay easy to override.
| Syntax | Specificity | Typical use |
|---|---|---|
:is(...) | That of the strongest selector in the list | Grouping without changing behavior |
:where(...) | Always zero | Base styles that stay easy to override |
A concrete example of the trap
/* the specificity of :is() here equals that of #main */
:is(#main, .secondary) p {
color: navy;
}
/* this simpler rule will NOT be able to win */
p {
color: crimson;
}Even when the targeted element actually matches .secondary and not #main, the whole rule still inherits the weight of the id present in the list. That behavior, often poorly anticipated, is what makes a rule written with :is() surprisingly hard to override.
:where() over :is(), unless there is a specific reason to inherit the weight of the strongest selector.When to prefer :not() over :is()
:is() and :not() answer different needs. :is() groups alternatives that should all trigger the same style, while :not() excludes one precise case from a broader group. They often get combined inside the same selector, for instance to target every heading except ones located inside a footer, while grouping several heading levels into a single rule.
:is(h2, h3, h4):not(footer *) {
scroll-margin-top: 80px;
}This rule adds scroll margin to every heading from level two through four, except the ones inside the footer, in a single line rather than several separate selectors.
Frequently asked questions
:is() and :where() be used with pseudo-classes like :hover?Yes, the list can contain any valid type of selector, including pseudo-classes, which makes it possible for example to group several states inside a single rule.
Yes, the list can contain compound selectors, such as combinations of classes or combinators, as long as each entry in the list remains a valid selector on its own.
With :is(), an invalid selector inside the list does not invalidate the others, the rule keeps working for the remaining valid selectors, unlike the behavior of a classic comma separated list in a plain selector.