CSS Nesting Explained: Native Nested Selectors
Native CSS nesting lets you nest selectors inside a parent rule without a preprocessor. How the syntax works, the & selector, and specificity gotchas.
CSS nesting lets you write a selector inside another selector’s curly braces, so related rules for a component live together in one block instead of being repeated at the top level. It’s built directly into the CSS engine — no preprocessor, no build step required to get the syntax to work in the browser.
.card {
padding: 1rem;
border-radius: 0.5rem;
h2 {
font-size: 1.25rem;
}
&:hover {
box-shadow: 0 4px 12px rgb(0 0 0 / 0.1);
}
}
That compiles, conceptually, to .card { ... }, .card h2 { ... }, and .card:hover { ... } — but you write it once, grouped by component, instead of repeating .card three times.
The & selector
The & symbol stands in for the parent selector, and it matters more than it looks like it should. Write a nested rule that starts with a type selector, like div { ... } on its own inside .card, and the browser treats it as a descendant selector — .card div. But a nested rule that needs to attach a pseudo-class, combinator, or compound directly onto the parent needs & to say so explicitly:
.card {
/* descendant selector — matches a div anywhere inside .card */
span {
color: gray;
}
/* attaches directly to .card itself */
&.featured {
border: 2px solid gold;
}
/* & can also appear mid-selector */
.theme-dark & {
background: #111;
}
}
That last example is worth calling out: & doesn’t have to be at the start. .theme-dark & { ... } nests a rule that applies when .card is a descendant of .theme-dark, which is the opposite relationship from a normal descendant selector and would be awkward to express without nesting at all.
Specificity works the way you’d expect — and that’s the gotcha
Nesting doesn’t add its own specificity. A nested rule has exactly the specificity you’d get if you’d written out the full selector chain by hand, per CSS specificity rules. The problem is that nesting hides the chain from you visually. .card .header .title { ... } looks intimidating; the same rule nested three levels deep just looks like indentation, but it carries the identical three-class specificity weight. Deeply nested code can quietly become hard to override without you noticing, simply because the cumulative selector is no longer visible in one line.
The practical fix is the same one Sass users have used for years: keep nesting shallow, generally two or three levels at most, and prefer nesting for state (&:hover, &.is-active) and small structural relationships over deep component hierarchies.
Native nesting vs Sass nesting
| Native CSS nesting | Sass nesting | |
|---|---|---|
| Build step | None — ships to the browser as-is | Requires a compiler |
& behavior | Represents the parent selector | Same concept, same symbol |
| Nesting properties (not just selectors) | Not supported | Supported via mixins/interpolation |
| Media queries nested inside a rule | Supported | Supported |
| Runtime cost | Parsed natively by the browser | Compiled away before shipping |
For most projects the two are functionally interchangeable for the common cases — nesting a hover state, a child element, or a media query. Sass still has an edge for things like nesting property names (font: { size: 1rem; weight: 600; }) and using variables and loops alongside nesting, but if your only reason for a Sass build step was the nesting syntax, native CSS now covers that ground directly.
Nesting media queries and other at-rules
At-rules nest cleanly too, which removes one of the more annoying reasons to jump back out to the top level of a stylesheet:
.card {
width: 100%;
@media (min-width: 768px) {
width: 50%;
}
}
This pairs naturally with container queries when you want a component’s layout to respond to its container rather than the viewport — the @container rule nests the same way. It also composes with modern layout tools like CSS Grid and Flexbox: nesting groups the responsive breakpoints for a grid alongside the grid’s base rules instead of scattering them across the file, so a component’s default layout and its breakpoint overrides stay adjacent in the source rather than living in separate blocks that are easy to lose track of as a stylesheet grows.
Nesting also plays well with CSS custom properties — a common pattern is redefining a custom property inside a nested state selector so a whole subtree of declarations picks up the new value without repeating them individually. Combined with logical properties for direction-aware spacing, this lets a single nested block express both a component’s default appearance and its responsive, stateful, and internationalized variants without the file sprawling into dozens of loosely related top-level rules.
Browser support and the fallback question
Native nesting is supported in current versions of all major browsers, but if a project needs to support noticeably older browser versions, that’s a real constraint worth checking before relying on it exclusively. Unlike many newer CSS features, there isn’t a graceful degradation path for nesting — an unsupported browser simply won’t apply nested rules, rather than falling back to some reasonable default. Projects that already run a Sass or PostCSS build step for other reasons can keep using preprocessor nesting as-is, since it compiles down to flat selectors regardless of browser support; projects reaching for nesting specifically to avoid a build step should confirm their target browser matrix first.
The takeaway
Native CSS nesting groups related selectors into one readable block, using & to reference the parent explicitly when a rule needs to attach directly to it rather than target a descendant. It doesn’t change specificity math — it just changes how much of that math is visible at a glance — so keep nesting shallow and let & do the work when you need to combine with pseudo-classes or reverse the selector relationship. If your project only reached for Sass to get nested selectors, native CSS nesting is very likely a drop-in replacement.
Tagged
Keep reading
Takina · · 4 min read CSS object-fit and object-position, Explained
object-fit controls how an image or video is cropped inside its box, and object-position controls which part of it stays visible. How they work together.
Takina · · 5 min read CSS inherit, initial, unset & revert Explained
CSS's four global keywords control where a property's value comes from. How inherit, initial, unset, and revert differ, with a comparison table.
Takina · · 4 min read Dynamic Viewport Units: dvh, svh, and lvh Explained
dvh, svh, and lvh fix the classic mobile vh bug where browser toolbars cut off full-height layouts. Here's what each unit measures and when to use it.