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
Problem with :is()
/* 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 */Solution with :where()
/* base.css */
:where(article, section) p {
color: gray;
}
/* theme.css — works */
p {
color: black;
}
/* :where() has zero specificity
any p wins in an override */Modern CSS resets use :where() for this reason. Tailwind, Open Props, and several popular resets use :where() to define base styles that never lock in the developer. The intention is: "this is the default, but anything you write easily overrides it."
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;
}Be careful with invalid selectors inside :is(). If you place an invalid selector in a comma-separated list in traditional CSS, the entire rule is ignored. With :is() and :where(), invalid selectors are silently discarded — the others continue to work. This is an advantage, but it can hide typos.
What you should take away from this
- :is() groups selectors and inherits the specificity of the strongest one in the list.
- :where() does the same but with zero specificity — always overridable.
- Use :where() in resets and base styles that should not lock in customization.
- Use :is() in components and interactive states where specificity matters.
- Both eliminate selector repetition — your CSS becomes smaller and more readable.
- Browser support: all modern browsers. No compatibility concerns.



