CSSCSS Nesting

CSS Nesting

Native CSS nesting lets you write child selectors directly inside a parent rule block, instead of repeating the parent selector over and over across separate flat rules. It ships as part of the CSS engine itself — no Sass, Less, or PostCSS plugin required — and browser support has matured quickly across evergreen browsers since it landed.

Before and after: the problem nesting solves

Without nesting, styling a component and its internal parts means repeating the component's selector for every related rule. It works, but it scatters the "shape" of a component across many separate blocks and makes the relationship between them harder to see at a glance.

CSS
/* Flat selectors — repetitive, and the relationship between
   .card, .card-title and .card:hover is only implicit */
.card {
  padding: 1rem;
  border: 1px solid #ddd;
  border-radius: 8px;
}

.card-title {
  font-size: 1.25rem;
  font-weight: 600;
}

.card:hover {
  border-color: #0066cc;
}

.card:hover .card-title {
  color: #0066cc;
}

With native nesting, the same styles can be written as one block, where indentation visually communicates the structure:

CSS
.card {
  padding: 1rem;
  border: 1px solid #ddd;
  border-radius: 8px;

  /* Nested descendant selector — no & needed, this behaves
     like ".card .card-title" */
  .card-title {
    font-size: 1.25rem;
    font-weight: 600;
  }

  /* Nested compound selector attached to the parent itself —
     the & IS required here, see below */
  &:hover {
    border-color: #0066cc;

    .card-title {
      color: #0066cc;
    }
  }
}
The & nesting selector
The & symbol represents the parent selector at the point it's used, and is what lets you combine a nested rule with the parent instead of treating it as a descendant. This matters a lot for pseudo-classes, pseudo-elements, and anything that needs to attach directly to the parent rather than select something inside it.

CSS
.button {
  background: #0066cc;
  color: white;

  /* &:hover means ".button:hover" — attached directly */
  &:hover {
    background: #0052a3;
  }

  /* &.is-disabled means ".button.is-disabled" */
  &.is-disabled {
    opacity: 0.5;
    pointer-events: none;
  }

  /* & followed by a space is a descendant combinator: ".button .icon" */
  & .icon {
    margin-right: 0.5em;
  }

  /* &::before means ".button::before" */
  &::before {
    content: '';
  }
}
When is & actually required?
A plain nested selector that starts with a type selector, class, ID, or attribute selector (like .card-title or img) is automatically treated as a descendant of the parent — no & needed. But a selector that starts with a pseudo-class, pseudo-element, or combinator token (like :hover, ::before, or > li) needs the parent reference spelled out, which is exactly what & provides. In practice, many developers write & explicitly everywhere for consistency, even where it's optional.
Nesting media queries and at-rules
At-rules like @media and @supports can be nested inside a selector block too, which keeps responsive tweaks for a component next to the component's base styles instead of in a separate media-query section far away in the file.

CSS
.sidebar {
  width: 100%;

  @media (min-width: 768px) {
    width: 260px;
  }

  @supports (container-type: inline-size) {
    container-type: inline-size;
  }
}
Combining multiple selectors

CSS
.nav-link {
  color: #333;

  /* Comma-separated nested selectors both attach via & */
  &:hover,
  &:focus-visible {
    color: #0066cc;
    text-decoration: underline;
  }
}
Deep nesting reads worse than flat CSS, even though it's technically unlimited
Nothing stops you from nesting five or six levels deep, but every level of nesting increases the effective specificity and length of the generated selector, and makes the rule harder to scan. Deeply nested CSS can end up just as hard to maintain as the deeply nested Sass everyone eventually learned to avoid.
Keep nesting shallow — two to three levels is a healthy ceiling
A good rule of thumb: nest one level for the component's internal parts, and one more for states like &:hover. If you find yourself nesting past three levels, it's usually a sign the inner selector deserves its own class and its own top-level rule instead.
Native nesting vs. Sass nesting
If you've used Sass, this syntax will feel immediately familiar — see Sass Variables & Nesting for a refresher. The two are close cousins, but not identical:
  • & requirement — Sass lets you omit & in more places and infers the relationship more loosely; native CSS nesting is stricter about requiring & for anything that isn't a plain type/class/ID/attribute selector.

  • Compile step — Sass nesting is compiled away before the browser ever sees it, so there was never a browser-support question. Native nesting is parsed and evaluated by the browser itself at runtime.

  • Interpolation — Sass supports building selector names dynamically (&-#{$modifier}); native CSS nesting has no equivalent, since it has no variables/logic layer of its own beyond custom properties.

  • Error handling — an invalid nested rule in Sass fails at build time with a clear compiler error; an invalid native nested rule is simply ignored by the browser at runtime, which can be harder to notice.

Note
If your project already runs Sass, you don't need to rush to rewrite nested rules — the two approaches are compatible and Sass will happily pass native-looking nesting straight through in most cases. Native nesting mainly matters when you want nesting in plain CSS with no build step at all.
Next
See how @property lets a custom property carry a real type: [@property — typed custom properties](/css/at-property).