CSS :where() Zero Specificity - Build Easy-to-Override Defaults
CSS :where() collapses any selector list to specificity (0,0,0). Use it for resets, framework defaults, and base typography that consumers can override with a single class.
Detailed Explanation
:where() Is :is() With No Specificity
:where() and :is() accept the same arguments and match the same elements. The difference is specificity weight: :is() inherits the highest specificity from its arguments, while :where() always reports (0, 0, 0).
:is(#main, .content) p { color: gray; } /* (1, 0, 1) */
:where(#main, .content) p { color: gray; } /* (0, 0, 1) */
Why You Want Zero
Zero-specificity rules are the easiest to override. Consumers don't have to escalate to ID selectors or !important; a single class wins:
/* Library default */
:where(button) {
appearance: none;
border: 1px solid currentColor;
padding: 0.5em 1em;
}
/* Consumer override — wins because (0, 1, 0) > (0, 0, 0) */
.cta { border: none; background: dodgerblue; }
This is why every modern CSS reset (Open Props, Pico, modern-normalize, Tailwind Preflight) wraps its base rules in :where().
Layered Defaults
Combine :where() with @layer to push defaults even further down the cascade:
@layer reset {
:where(h1, h2, h3) { line-height: 1.1; margin-block: 0; }
}
The reset layer sits below all unlayered rules, and the :where() ensures even within the layer the score is 0.
Pitfall: Errors Still Propagate Selectivity
Although :where() is forgiving (invalid args are dropped silently), it does not zero out specificity for sibling selectors in the same rule:
:where(.x) #id { color: red; } /* Specificity: (1, 0, 0) — the #id stands */
Only what's inside the parentheses gets flattened.
Browser Support
Chrome 88+, Firefox 78+, Safari 14+. Safe for production today.
Use Case
Use :where() any time you ship CSS that other developers may need to override: design system tokens, prose typography (.markdown, .article), reset layers, or theme defaults. It removes the specificity-arms-race problem before it starts.