:is() and :where(): Selector Lists Without the Copy-Paste
You've written this stylesheet stutter:
article h1, article h2, article h3,
section h1, section h2, section h3 {
scroll-margin-top: 80px;
}
Six selectors for one idea. With :is(), the idea gets written once:
:is(article, section) :is(h1, h2, h3) {
scroll-margin-top: 80px;
}
:is() matches if any selector in its list matches - it distributes like multiplication over addition. Two short lists replace the six-way cross product, and adding h4 later is a one-word edit instead of two.
:where() - the same thing with a superpower disguised as a weakness
:is() takes the specificity of its most specific argument. :where() always contributes zero. That sounds like trivia until you write base styles other people (including future you) need to override:
/* Base link styling with ZERO specificity weight from the :where part */
:where(.prose, .comment, .card-body) a {
color: #4f46e5;
text-underline-offset: 2px;
}
/* Later, anywhere: a plain class wins - no !important, no specificity war */
.danger-link { color: crimson; }
Had that base rule used :is(.prose, .comment, .card-body), its specificity would include a class, and .danger-link would lose. This is why every modern CSS reset is wrapped in :where() - defaults that yield gracefully. If you ship any kind of base layer or theme, :where() is your politeness tool: full styling power, no specificity footprint blocking downstream overrides.
The forgiving list: one bad selector no longer kills the group
Classic CSS drops an entire rule if any selector in the comma list fails to parse - the silent killer when mixing vendor pseudo-elements:
::-webkit-slider-thumb, ::-moz-range-thumb { ... } /* BOTH die in every browser */
:is(::-webkit-slider-thumb)... /* (pseudo-elements aren't valid in :is, bad example there) */
Inside :is()/:where(), unknown or unsupported selectors are simply ignored while the rest match on - "forgiving" parsing. (Pseudo-elements specifically stay outside; the forgiveness applies to element/class/pseudo-class selectors, including experimental ones you're feature-testing.) Writing :where(:has(img), .has-img) style fallback pairs is safe in a way plain comma lists never were.
Everyday combos
.menu > :is(a, button) { padding: 8px 14px; } /* mixed children, one rule */
:is(h2, h3, h4):hover .anchor-link { opacity: 1; } /* shared hover behavior */
input:is(:focus-visible, .invalid) { border-color: currentColor; } /* state OR class */
Support: everywhere since 2021. These two read like syntax sugar, but :where()'s zero-specificity behavior is genuinely strategic - it's the difference between a design system people extend and one they fight.
Full-stack web developer sharing practical tutorials and building tools that ship.
Got something on your mind?
My inbox is open - no forms disappearing into the void here.
- Just say hello Found a tutorial useful? Spotted a mistake? Tell me.
- Hire me for a project Have something custom in mind? Let's talk scope and timelines.
- Product support Bought something here? I'll help you get it running.
I usually reply within 1-2 business days.