CSS :where() and :is(): Specificity, Resets, and Maintainability
Many developers overlook the CSS pseudo-classes :where() and :is(), seeing them as "syntax sugar". This article reveals how crucial they are for solving specificity chaos, enabling you to create scalable and maintainable CSS. Understand the difference and apply them in resets, components, and design systems.

Have you ever written the same CSS block three times, only changing the selector? That's exactly the pain that
:is()and:where()solve — and the difference between the two is more important than it seems.
Does this CSS look familiar to you:
header a,footer a,nav a { color: blue; text-decoration: none;}Repeating the same rule for three different contexts. Now imagine you have ten contexts. And each one has three state variations — :hover, :focus, :visited. The CSS file turns into a phone book.
Modern CSS has an answer for this. Two, actually — and they look the same but behave differently in a detail that will save you from subtle bugs.
:is() — group selectors without repetition
The :is() accepts a list of selectors and applies the style to any one that matches. The block above becomes:
:is(header, footer, nav) a { color: blue; text-decoration: none;}One line, same result. The browser interprets this exactly like the repeated version — semantically they are equivalent.
It gets more interesting when you combine it with pseudo-classes:
/* Before — repeating for each state */button:hover,button:focus,button:active { background: darkblue;}/* After — clean */button:is(:hover, :focus, :active) { background: darkblue;}Or to style headings all at once:
/* All headings within article */article :is(h1, h2, h3, h4) { font-family: Georgia, serif; line-height: 1.3;}:where() — identical, but without specificity weight
Here lies the difference that matters.

Specificity in CSS is the scoring system that decides which rule wins when two contradict each other. An ID selector is worth more than a class, which is worth more than a tag. When you use :is(), the specificity of the strongest selector inside the parentheses propagates to the entire rule.
/* :is() inherits the specificity of #header — high */:is(#header, .nav, footer) a { color: blue;}/* ↑ this selector has ID specificity because of #header, even if the element is .nav or footer */The :where() works the same in behavior — but it has zero specificity. Always. No matter what you put inside.
/* :where() — zero specificity */:where(#header, .nav, footer) a { color: blue;}/* ↑ easy to override — any class or tag selector already wins */In practice, this means that :where() is ideal for base styles and resets — you define them without locking in the developer who will customize them later.

The real-world difference
/* base.css */:is(article, section) p { color: gray;}/* theme.css — will not work */p { color: black;}/* :is(article, section) p has higher specificity than p *//* base.css */:where(article, section) p { color: gray;}/* theme.css — works */p { color: black;}/* :where() has zero specificity any p wins in an override */An example that combines both
In a design system, you would use both in different layers:
/* Base layer — :where() to avoid locking anyone in */:where(h1, h2, h3, h4, h5, h6) { margin: 0 0 0.5em; line-height: 1.2;}/* Component layer — :is() for specific contexts */.card :is(h2, h3) { font-size: 1.25rem; color: var(--color-heading);}/* Interactive states — :is() because weight matters */.btn:is(:hover, :focus-visible) { outline: 2px solid currentColor; outline-offset: 2px;}

