Articles

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.

Takina Takina · · 5 min read
A close-up of CSS markup with nested selectors and gradient values

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 nestingSass nesting
Build stepNone — ships to the browser as-isRequires a compiler
& behaviorRepresents the parent selectorSame concept, same symbol
Nesting properties (not just selectors)Not supportedSupported via mixins/interpolation
Media queries nested inside a ruleSupportedSupported
Runtime costParsed natively by the browserCompiled 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.

Takina 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.

#CSS #Web Development #Frontend
Takina 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.

#CSS #Web Development #Frontend
Takina 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.

#CSS #Web Development #Frontend