:not(), :is() & :where()
CSS selectors can get long and repetitive once a page grows past a handful of components. Three "functional" pseudo-classes — :not(), :is(), and :where() — let you write selector lists more compactly and, in the case of :not() and :is(), exclude or combine matches without duplicating whole selector chains. Each one accepts a selector (or a comma-separated list of selectors) as its argument.
:not() — exclude matches
:not(selector) matches any element that does NOT match the selector passed to it. It is the negation pseudo-class, and it can take a single selector or, in modern browsers, a comma-separated list.
/* Every button except the one with class .primary */
button:not(.primary) {
background: #eee;
color: #333;
}
/* Every list item that is not the last child */
li:not(:last-child) {
border-bottom: 1px solid #ddd;
}
/* Modern :not() accepts a selector list */
input:not([type="checkbox"], [type="radio"]) {
padding: 0.5em;
}:is() — match any of several selectors
:is(selector-list) matches an element if it matches ANY selector in the list. It is most useful for collapsing long, repetitive selector groups into a single, readable rule.
/* Before: repetitive and hard to scan */
header nav ul li a,
header nav ol li a,
footer nav ul li a,
footer nav ol li a {
text-decoration: none;
}
/* After: :is() collapses the repeated parts */
:is(header, footer) nav :is(ul, ol) li a {
text-decoration: none;
}Both rules above target the exact same elements. The :is() version is shorter, easier to maintain, and communicates intent — "either a header or a footer, containing either an ordered or unordered list."
:where() — same matching, zero specificity
:where() accepts the exact same syntax as :is() and matches exactly the same elements. The difference is entirely about specificity.
/* :is() specificity = specificity of ".card" (0,1,0) */
:is(section, .card) p {
color: #333;
}
/* :where() specificity = 0, regardless of contents */
:where(section, .card) p {
color: #333;
}
/* A consumer of a design system can override the :where()
version with a single-class selector — no fighting the
library's specificity budget. */
.card p {
color: firebrick;
}Feature | :is() | :where() |
|---|---|---|
Matches | Any selector in the list | Any selector in the list (identical) |
Specificity | Specificity of its most specific argument | Always zero, regardless of arguments |
Typical use | Shortening repetitive selectors | Writing override-friendly base/reset styles |
Invalid selector in list | Whole rule is ignored (unless using the forgiving list) | Whole rule is ignored (unless using the forgiving list) |
Combining them
Use
:is()to shorten repetitive selector groups you write and control yourself.Use
:where()when writing library, reset, or "default" styles that consumers should be able to beat easily.Use
:not()to exclude specific matches from an otherwise broad selector.All three accept selector lists, so they compose well with each other, e.g.
:not(:is(.card, .panel)).