CSS :is() Pseudo-Class Specificity Explained
This article explains how the CSS :is() pseudo-class
determines selector specificity by dynamically inheriting the weight of
its most specific argument. You will learn the mechanics behind
specificity calculation in CSS selectors, why matching an element via
:is(.a, #b) behaves with the specificity of an ID selector
regardless of which selector matches, and how this behavior affects the
cascade in real-world stylesheets.
How the Specificity of
:is() Works
The :is() pseudo-class simplifies complex selectors by
allowing you to group multiple targets into a single rule. However,
unlike traditional grouping separated by commas, the browser treats the
specificity of the entire :is() expression as equal to the
selector inside its argument list that possesses the highest
specificity.
Specificity in CSS is calculated using three categories:
- A (ID selectors): e.g.,
#bhas a score of(1, 0, 0). - B (Class, attribute, and pseudo-class selectors):
e.g.,
.ahas a score of(0, 1, 0). - C (Type selectors and pseudo-elements): e.g.,
divhas a score of(0, 0, 1).
The :is() pseudo-class itself contributes zero
specificity (0, 0, 0) to the calculation. Instead, the CSS
parser scans the comma-separated arguments provided inside the
parentheses, evaluates the specificity of each, and assigns the maximum
value found to the entire :is() block.
Analyzing :is(.a, #b)
When you declare a selector such as:
:is(.a, #b) {
color: red;
}The browser evaluates the two arguments:
.ahas a specificity of(0, 1, 0).#bhas a specificity of(1, 0, 0).
Because (1, 0, 0) is higher than (0, 1, 0),
the browser sets the specificity of :is(.a, #b) to
(1, 0, 0).
Crucially, this score applies statically to all matching elements,
regardless of which individual selector matches the element in the DOM.
Even if an element only matches the .a class and has no
relation to #b, it is evaluated with the specificity of an
ID selector.
Practical Example: The Specificity Trap
Consider the following HTML:
<p class="a">This text will be red.</p>And the following CSS:
:is(.a, #b) {
color: red;
}
p.a {
color: blue;
}Intuitively, you might expect p.a (specificity:
0, 1, 1) to override .a inside
:is() because p.a includes both an element and
a class selector.
However, the text renders as red. Because
#b is included in the :is() arguments,
:is(.a, #b) has a specificity of (1, 0, 0).
Since (1, 0, 0) outranks (0, 1, 1), the
:is() rule wins. To override this rule without an ID, you
would need another selector with at least one ID or an
!important declaration.
Alternative: When to Use
:where()
If you need the grouping capabilities of :is() without
adopting the highest specificity of the argument list, use the
:where() pseudo-class instead:
:where(.a, #b) {
color: red;
}The :where() pseudo-class always sets its total
specificity to (0, 0, 0), making it easy to override
anywhere else in your stylesheet. Use :is() when you want
the group to maintain a high priority in the cascade, and use
:where() when you want low-specificity defaults.