Articles /
:is() and :where() selectors
Two pseudo-classes that reduce selector repetition. They look identical but have a critical difference — specificity.
Reducing selector repetition
Before :is(), matching the same rule for multiple parents meant writing every combination by hand.
/* old way — verbose */
article h2, section h2, aside h2 {
color: oklch(35% 0.12 260);
}
/* with :is() — compact */
:is(article, section, aside) h2 {
color: oklch(35% 0.12 260);
}
Specificity: the key difference
:is() takes the specificity of its most specific argument. :where() always contributes zero specificity.
/* :is() specificity = (1, 0, 1) because #main is an ID */
:is(#main, footer) p { color: oklch(30% 0 0); }
/* :where() specificity = (0, 0, 1) — only the trailing p counts */
:where(#main, footer) p { color: oklch(50% 0 0); }
:where() for defaults you expect consumers to override, and :is() when you want normal specificity behavior.Forgiving selector lists
Both :is() and :where() use forgiving parsing. If one selector in the list is invalid, the rest still work. A regular comma-separated selector list would discard the entire rule.
/* :unsupported-pseudo is invalid, but the rule still applies to .card */
:is(.card, :unsupported-pseudo) {
border: 1px solid oklch(80% 0 0);
}
/* without :is(), the entire rule is dropped */
.card, :unsupported-pseudo {
border: 1px solid oklch(80% 0 0); /* ignored! */
}
Practical patterns
These patterns show up constantly in production codebases.
/* style all headings inside prose */
.prose :is(h1, h2, h3, h4) {
font-family: var(--font-display);
letter-spacing: -0.02em;
color: oklch(25% 0.05 260);
}
/* reset-layer defaults with :where() */
@layer reset {
:where(ul, ol) { list-style: none; padding: 0; }
:where(a) { color: inherit; text-decoration: none; }
}
/* interactive states */
.btn:is(:hover, :focus-visible) {
background: oklch(50% 0.25 270);
}
Using with CSS nesting
Native CSS nesting implicitly wraps parent selectors in :is(). Understanding this prevents specificity surprises in nested rules.
.card {
padding: 1.5rem;
/* compiles to :is(.card) h2 — specificity (0, 1, 1) */
h2 { font-size: 1.25rem; }
/* compiles to :is(.card):hover — specificity (0, 2, 0) */
&:hover {
box-shadow: 0 4px 12px oklch(0% 0 0 / 0.1);
}
}
:is() under the hood, a nested .card h2 may have different specificity than the same selector written flat. Keep this in mind when debugging.Browser support and summary
Both :is() and :where() are supported in all modern browsers since 2021. There is no reason to avoid them in new projects.
- Use
:is()for general selector grouping. - Use
:where()for low-specificity defaults and resets. - Leverage forgiving parsing when mixing stable and experimental selectors.
- Be aware of specificity escalation when
:is()contains IDs.