Both :is() and :where() solve the same repetition problem, matching identical elements for identical selector lists. The single axis they diverge on — specificity — is exactly the axis that decides which one belongs in your stylesheet.
2The One Difference That Matters
:is() computes its specificity the same way a normal selector list would — it takes on the specificity of its single most specific argument. :is(.nav, #header) a therefore has the specificity of an ID selector, since #header outweighs .nav.
:where(), introduced specifically to solve this, always contributes exactly zero specificity, no matter what's inside it — :where(#header) still counts as zero. This isn't a bug or edge case; it's the entire reason :where() exists as a distinct feature from :is().
3A Practical Decision Rule
Default to :where() any time you're writing base styles, resets, or design-system defaults meant to be freely overridden by consuming code — its zero specificity guarantees that even the simplest later class selector wins without a fight. Reach for :is() when you're grouping selectors purely to avoid repetition in your own, self-contained styles, where you're not particularly concerned about external overrides and want the specificity to behave the way it normally would.
A useful mental shortcut: :where() says 'match these, but don't fight for priority'. :is() says 'match these, and take the priority you'd normally get'.
4Step-by-Step Breakdown
Two Selectors, One Job, One Critical Difference. :is() and :where() both let you group a list of selectors to avoid repeating a shared context — :is(h1, h2, h3) a instead of h1 a, h2 a, h3 a. They match exactly the same elements. The single difference that decides which one you should reach for is specificity.
Grouping Selectors To Eliminate Repetition. Both functions accept a comma-separated selector list and match any element that matches any selector in that list, letting you factor out a shared ancestor or descendant instead of writing near-duplicate rules for every combination.
Selector Grouping. What do :is(header, footer, nav) a and writing header a, footer a, nav a separately have in common?
- →They always produce identical specificity
- →They match exactly the same set of elements
- →Nothing — they're unrelated features
:is() Takes The Highest Specificity Of Its Arguments. :is() behaves like the selector list was 'the strongest one wins' for specificity purposes: :is(#header, .nav) a carries the specificity of an ID selector, even though only one of its two arguments is an ID. This can produce surprisingly high-specificity rules from what looks like a convenience grouping.
:is() Specificity. What is the effective specificity contribution of :is(#header, .nav)?
- →The average of an ID and a class selector
- →That of an ID selector — the highest-specificity argument in the list
- →Zero — :is() never contributes specificity
:where() Always Contributes Zero Specificity. :where() matches identically to :is() but always contributes zero specificity, regardless of what's inside it — even :where(#header) counts as specificity zero. This makes it the ideal tool for base styles or design-system defaults meant to be trivially overridden by literally any other rule.
Choosing :where(). Why would a component library author prefer :where() over :is() for their default base styles?
- →Because :where() is faster to parse than :is()
- →Because :where()'s zero specificity guarantees consumers can override the defaults with even a single low-specificity class
- →There's no meaningful reason — they're interchangeable
Grouping Selectors Mastered. You now know when to reach for each: :is() when you want the grouped selector's specificity to reflect its strongest argument, and :where() when you deliberately want zero specificity so the rule stays maximally easy to override — exactly the property a design system's base styles need.
Group Selectors With :is(). :is() lets you group multiple selectors into one, applying the same declarations to all of them.
Level Up 🚀
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Zero-Specificity Base Styles Reduce The Risk Of Accidentally Unoverridable Accessibility Fixes
If a design system's default styles use :where(), a consumer's targeted focus-state or contrast fix is guaranteed to apply without needing !important or a specificity war, keeping accessibility patches simple to ship.
2Grouped Selectors Can Simplify Auditing Focus Styles Across Many Interactive Elements At Once
Using :is(button, a, input, select):focus-visible to define one consistent focus treatment reduces the chance any single interactive element type is accidentally missed during an accessibility pass.
SEO Implications
- 1
Selector Grouping Reduces Total Stylesheet Size Through Less Repetition
Replacing many near-duplicate selectors with a single :is() or :where() group can measurably shrink a compiled stylesheet, directly reducing the CSS payload every page load has to fetch and parse.
- 2
:where()-Based Component Libraries Ship Fewer Specificity-Escalation Overrides Downstream
When a component library's defaults use :where(), consuming projects need less counter-specificity CSS to override them, keeping the final shipped stylesheet leaner across the whole site.
Best Practices
Default Component-Library And Reset Styles To :where()
This guarantees consumers can override any default with the simplest possible selector, which is exactly the ergonomics a shared base layer should offer.
Use :is() Freely Within Your Own Application Styles To Reduce Repetition
Since you control both sides of the specificity relationship in your own codebase, :is()'s normal specificity behavior is rarely a problem and the repetition savings are worth it.
Frequent Bugs
A design-system base style can't be overridden by a consuming project no matter how the selector is written.
The base style used :is() with a high-specificity argument (like an ID) instead of :where(), which always contributes zero specificity and would have avoided the conflict entirely.
A grouped selector using :is() unexpectedly outranks a rule that should have won by source order.
Check whether one of :is()'s arguments has unexpectedly high specificity (like an ID) — that becomes the whole group's specificity, not an average or the lowest argument.
Real-World Examples
A Freely Overridable Component Library Reset
A shared component library resets typography defaults using :where() so every consuming application can override them with ordinary, low-specificity class selectors.
:where(.btn, .badge, .chip) {
font-family: inherit;
line-height: 1.2;
}