Skip to content
AgileLogics
6 min read

CSS Nesting

Prerequisites: Cascade layers

Native CSS now supports nesting — rules inside rules, as in Sass — in all major browsers (Chrome and Edge 120, Firefox 117, Safari 17.2 for the current relaxed syntax).

.card {
  padding: 1rem;
  border: 1px solid #ddd;

  & h2 {
    margin-top: 0;
  }

  p {
    color: #444;
  }

  &:hover {
    border-color: #0b57d0;
  }

  &.featured {
    border-width: 2px;
  }

  .dark-theme & {
    border-color: #444;
  }

  @media (width >= 40rem) {
    padding: 2rem;
  }
}
  • & stands for the parent selector. &:hover is .card:hover; &.featured is .card.featured; .dark-theme & is .dark-theme .card.
  • A nested selector without & (like p) is treated as a descendant: .card p.
  • Media queries and other conditional rules can nest directly and apply to the parent selector.

How nested selectors are matched

The browser treats a nested rule like :is(parent) nested. That has two consequences Sass users should know:

<style>
  #hero, .banner {
    & p { color: rgb(255, 0, 0); }
  }
  .banner p { color: rgb(0, 0, 255); }
</style>
<div class="banner"><p>Text</p></div>
<script>
  const rule = document.styleSheets[0].cssRules[0].cssRules[0];
  console.log(rule.selectorText);
  console.log(getComputedStyle(document.querySelector(".banner p")).color);
</script>
& p
rgb(255, 0, 0)
  • The CSSOM keeps the nested selector as written (& p), but it matches like :is(#hero, .banner) p, so its specificity is 1,0,1 even for the .banner match — it beat the later .banner p.
  • You can't concatenate strings: &__title does not produce .card__title as in Sass. BEM-style suffixes still need a preprocessor or full class names.

Guidelines

  • Nest one or two levels at most. Deep nesting produces long, specific selectors — the same problems as writing them by hand.
  • Nest states (&:hover, &:focus-visible), modifiers, and media queries; avoid nesting just to mirror the HTML structure.
  • Use a build tool (Lightning CSS, PostCSS) to flatten nesting only if you must support browsers from before late 2023.

Practice

Rewrite this with nesting:

.nav { display: flex; }
.nav a { color: white; }
.nav a:hover { text-decoration: underline; }
@media (width < 40rem) { .nav { flex-direction: column; } }
Show solution
.nav {
  display: flex;

  a {
    color: white;

    &:hover {
      text-decoration: underline;
    }
  }

  @media (width < 40rem) {
    flex-direction: column;
  }
}