Modern selectors and cascade control
:is, :where, :has, nth-child of, native nesting, cascade layers and scoped styles.
is, where and has
Three functional pseudo-classes removed most of the reasons to write long selector lists. :is() groups alternatives, :where() does the same with no specificity cost, and :has() finally lets a parent react to its children.
:is(h1, h2, h3) { line-height: 1.15; } /* specificity of the most specific argument */
:where(ul, ol)[class] { list-style: none; } /* zero specificity — trivially overridden */
.card:has(> img) { padding-block-start: 0; }
.form:has(:invalid) { border-color: var(--danger); }
li:nth-child(3 of .featured) { font-weight: 600; }| Selector | Matches | Specificity |
|---|---|---|
:is(a, b) | Either alternative | That of the most specific argument |
:where(a, b) | Either alternative | Zero |
:has(x) | Elements containing a match | That of the most specific argument inside |
:not(x) | Everything except a match | That of the argument |
:nth-child(3 of S) | The third child matching S | Only the pseudo-class counts |
:focus-visible | Keyboard focus only | One class |
Native nesting
Nesting is a shorthand for writing descendant selectors. It compiles to the same flat selectors you would have written by hand, which means the specificity is also the same — nesting introduces no extra weight of its own.
.card {
padding: 1rem;
& .title { font-size: 1.25rem; }
&:hover { box-shadow: 0 4px 12px rgb(0 0 0 / 0.12); }
&[data-state="open"] { border-color: var(--brand); }
@media (min-width: 48rem) { padding: 2rem; }
}&is required when the nested selector starts with an element name or a combinator, otherwise it reads as a descendant selector.- Nesting compiles at build time in every modern browser; there is no runtime cost.
- Cap nesting at two or three levels — deeper trees are the same unreadable descendant chains, just indented.
- Media queries and container queries can be nested too, which keeps a component's responsive rules in one place.
Cascade layers and scope
Layers add a level above specificity: the order of layers decides the winner before specificity is ever compared. That makes the third-party stylesheet question answerable once, in one line, instead of with an escalating !important.
@layer reset, base, components, utilities;
@layer base {
a { color: inherit; text-decoration-thickness: 1px; }
}
@layer components {
.btn { background: var(--brand); color: white; }
}
@layer utilities {
.text-brand { color: var(--brand); }
}
@scope (.card) to (.card__body) {
img { border-radius: 4px; }
}The layer order is fixed by that first declaration, so later @layer blocks do not need to repeat it. @scope answers a different question: it limits a rule to a subtree and gives proximity a vote, which is useful for content blocks you do not fully control.
FAQ
Can I nest :has() inside :has()?
:has() are not allowed inside :has(), and the check must be cheap because the browser evaluates it for every candidate element.Do layers replace BEM?
Related
CSS architecture, naming and design tokens Units, custom properties and calc
Last refreshed 2026-09-18.