AniUI Academy

Native CSS Nesting

Writing nested selectors directly in CSS — no Sass, no preprocessor — with the `&` parent reference, nested at-rules, and a few honest gotchas about what native nesting won't do.

9 min read

Nesting selectors inside each other was the single most popular reason to adopt a CSS preprocessor for a decade. Being able to write a component's base styles, its hover state, its focus state, and its media queries in one block instead of five was worth a whole Sass or LESS build step for most teams.

Native CSS now does it directly. No preprocessor, no build step, no .scss extension — the browser parses nested rules the same way it parses any other CSS.

The base syntax, with and without &

Two shapes to get comfortable with:

/* Plain descendant nesting — `&` is optional. */
.card {
  background: white;
 
  h2 {
    font-size: 1.25rem;
  }
 
  p {
    line-height: 1.6;
  }
}

The h2 and p rules apply to descendants of .card. You can write the parent reference explicitly if you want (& h2 { ... }) but it's not required for the descendant case.

The parent reference is required for compound selectors that attach to the parent itself:

.button {
  background: var(--brand);
 
  &:hover {
    background: var(--brand-dark);
  }
 
  &.primary {
    font-weight: 600;
  }
 
  &[disabled] {
    opacity: 0.5;
  }
}

&:hover means .button:hover, not .button :hover. The space-vs- no-space is load-bearing; & lets you make the distinction clearly.

The reversed form

You can put & in any position, not just at the start. The common use is "I want this component to behave differently inside a specific ancestor context":

.icon {
  color: currentColor;
 
  /* Inside a dark-themed wrapper, show the icon brighter. */
  .dark &  {
    color: var(--brand-light);
  }
}

That compiles to the equivalent of .dark .icon. The & is in the right place for a reader to see "this component, inside a dark context."

Nested at-rules

The quietly useful feature: @media, @container, @supports, and others nest directly, and the parent selector applies inside the at-rule's body:

.card {
  display: block;
 
  @container (min-width: 500px) {
    display: flex;
  }
 
  @media (prefers-reduced-motion: no-preference) {
    transition: transform 0.2s ease;
  }
}

The media and container queries sit next to the rule they modify, not somewhere else in the file. For a component whose responsive behaviour is a small tweak on a base rule, this is the shape most worth adopting.

The specificity gotcha worth knowing

Native CSS nesting isn't textual substitution the way Sass is. When the browser evaluates a nested selector, it wraps the parent selector list in :is(...). That has one specific consequence:

.a, h1 {
  & span {
    color: red;
  }
}

The nested & span has the specificity of :is(.a, h1) span, which is calculated using the most-specific selector in the parent list — the class. So the effective specificity of & span here is one class plus one type selector. If h1 span were written alone, it would have the specificity of two type selectors, which is lower.

This almost never matters in practice because most nested rules have a single-selector parent. It matters enough to know about if you're porting a Sass stylesheet that mixed classes and type selectors in the same parent list, where Sass would have unrolled each parent individually.

Nesting depth is still a code-smell knob

Just because you can doesn't mean you should. Deeply nested native CSS has the same two problems Sass's deeply nested CSS had:

  • Specificity grows with each level. .card .body .title is harder to override than .card-title.
  • Searchability drops. A reader looking for .title finds many hits; a reader looking for the specific .title inside this card's body finds nothing without opening every file that has a .card rule.

The useful rule, same as the Sass era: one or two levels of nesting is a readability win. Three or more starts paying interest. Nesting a component's states (&:hover, &:focus), its immediate child elements (& .header, & .body), and its media / container queries is almost always fine. Nesting a .grand-child inside a .child inside a .parent is almost always worse than giving each of those a flat, named class.

Browser support and preprocessor interop

Native nesting is baseline-available — all major browsers support it. You can reach for it the same way you reach for Grid or Flexbox.

If your codebase still uses Sass, the two coexist cleanly: Sass nesting continues to work and continues to produce textually unrolled CSS; native nesting works in files you write without .scss as well. The practical migration path most teams follow is "stop nesting in Sass for new rules," not "rewrite everything overnight."

A small gotcha for the Sass crowd: the two differ in the specificity detail described above, and in a handful of edge cases around selector composition. New code written as native should read documentation (MDN's "CSS nesting" page is the clearest reference) rather than assume Sass muscle memory transfers perfectly.

What to remember

  • & is the parent-reference symbol. Required for compound selectors (&:hover) and reversed-position selectors (.dark &); optional for plain descendants.
  • At-rules (@media, @container, @supports) nest directly and keep the parent selector in scope — this is the single biggest readability win.
  • Specificity gotcha: a parent selector list is wrapped in :is(), so the specificity of the nested selector reflects the most-specific parent, not each parent individually. Rarely bites with single-selector parents.
  • Keep nesting shallow — one or two levels. Three or more produces fragile, high-specificity selectors and is the same anti-pattern as deeply-nested Sass.
  • Native nesting is baseline-available. Coexists with Sass; new code can be written as native without touching existing Sass files.

Check yourself

4 questions · pass 3/4 to unlock The Cascade and Inheritance

up to 50
  1. 1.In native CSS nesting, what does the & symbol refer to?

  2. 2.What's an important gotcha about how a nested selector is actually compiled under native CSS?

  3. 3.Can you nest @media and @container queries directly inside a rule?

  4. 4.A naive port of a deeply-nested Sass stylesheet gives you .a { .b { .c { .d { color: red; } } } }. What's the right habit to form when writing native nesting from scratch?

4 left to answer

Discussion

Sign in to post
Sign in to join the conversation.Sign in

No comments yet. Be the first to say something.