CSS Nesting: Native Nesting Without Preprocessors

Shunku

For years, one of the main reasons developers used CSS preprocessors like Sass or Less was nesting. Writing nested selectors feels natural and keeps related styles together. Now, native CSS supports nesting, and you can use it without any build tools.

What is CSS Nesting?

CSS nesting allows you to write child selectors inside their parent selectors:

/* Traditional CSS */
.card {
  padding: 1rem;
}
.card .title {
  font-size: 1.5rem;
}
.card .title:hover {
  color: blue;
}

/* With CSS Nesting */
.card {
  padding: 1rem;

  .title {
    font-size: 1.5rem;

    &:hover {
      color: blue;
    }
  }
}

The nested version is more readable and clearly shows the relationship between selectors.

flowchart TD
    subgraph Traditional["Traditional CSS"]
        A[".card { }"]
        B[".card .title { }"]
        C[".card .title:hover { }"]
    end

    subgraph Nested["CSS Nesting"]
        D[".card {"]
        E["  .title {"]
        F["    &:hover { }"]
        G["  }"]
        H["}"]
    end

    style Nested fill:#10b981,color:#fff

The Nesting Selector (&)

The & character represents the parent selector. It's required in some cases and optional in others.

When & is Required

You need & when adding pseudo-classes or pseudo-elements:

.button {
  background: blue;

  /* Pseudo-classes need & */
  &:hover {
    background: darkblue;
  }

  &:focus {
    outline: 2px solid orange;
  }

  /* Pseudo-elements need & */
  &::before {
    content: "β†’ ";
  }

  /* Combining with other selectors */
  &.primary {
    background: green;
  }
}

When & is Optional

For descendant selectors (selecting children), & is optional:

.card {
  /* These are equivalent */
  .title { font-weight: bold; }
  & .title { font-weight: bold; }
}

Both compile to .card .title { font-weight: bold; }.

Placing & After the Nested Selector

You can place & after another selector to reverse the relationship:

.title {
  font-size: 1rem;

  /* When .title is inside .hero */
  .hero & {
    font-size: 2rem;
  }
}

This compiles to:

.title { font-size: 1rem; }
.hero .title { font-size: 2rem; }

Nesting Multiple Selectors

Selector Lists

You can nest multiple selectors at once:

.card {
  h1, h2, h3 {
    margin-top: 0;
  }

  &:hover, &:focus {
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  }
}

Compound Selectors

When combining selectors, use &:

.button {
  /* .button.primary */
  &.primary {
    background: blue;
  }

  /* .button.secondary */
  &.secondary {
    background: gray;
  }

  /* .button[disabled] */
  &[disabled] {
    opacity: 0.5;
  }
}

Nesting At-Rules

One powerful feature is nesting at-rules like @media inside selectors:

.sidebar {
  width: 100%;

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

  @media (min-width: 1024px) {
    width: 400px;
  }
}

This compiles to:

.sidebar { width: 100%; }
@media (min-width: 768px) { .sidebar { width: 300px; } }
@media (min-width: 1024px) { .sidebar { width: 400px; } }

This keeps all styles for a component together, rather than scattering them across multiple media query blocks.

Nesting Container Queries

The same works with container queries:

.card {
  display: block;

  @container (min-width: 400px) {
    display: flex;
  }
}

Nesting @supports

.grid {
  display: flex;
  flex-wrap: wrap;

  @supports (display: grid) {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
  }
}

Deep Nesting: Use with Caution

While nesting is powerful, avoid going too deep:

/* Avoid: Too deeply nested */
.page {
  .header {
    .nav {
      .menu {
        .item {
          .link {
            color: blue;
          }
        }
      }
    }
  }
}

This creates overly specific selectors that are hard to override. Keep nesting to 2-3 levels maximum:

/* Better: Flatter structure */
.nav-menu {
  display: flex;

  .item {
    padding: 0.5rem;
  }

  .link {
    color: blue;

    &:hover {
      color: darkblue;
    }
  }
}

Comparing with Sass Nesting

If you're familiar with Sass, CSS nesting is very similar:

Feature Sass CSS Nesting
Descendant nesting .parent { .child { } } .parent { .child { } }
Pseudo-classes &:hover { } &:hover { }
Compound selectors &.class { } &.class { }
Parent reference after .context & { } .context & { }
Nested media queries @media { } @media { }
Property nesting font: { size: 1rem; } Not supported
String interpolation #{$var} Not supported

The main differences are that CSS nesting doesn't support property nesting or variable interpolation in selectors.

Practical Example: Component Styling

Here's a complete component styled with nesting:

.card {
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  overflow: hidden;

  /* Image area */
  .image {
    aspect-ratio: 16 / 9;
    object-fit: cover;
  }

  /* Content area */
  .content {
    padding: 1rem;
  }

  /* Title styling */
  .title {
    margin: 0 0 0.5rem;
    font-size: 1.25rem;

    &:hover {
      color: var(--primary-color);
    }
  }

  /* Description */
  .description {
    color: #666;
    line-height: 1.6;
  }

  /* Footer with actions */
  .footer {
    display: flex;
    justify-content: space-between;
    padding: 1rem;
    border-top: 1px solid #eee;
  }

  /* Interactive states */
  &:hover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  }

  /* Variants */
  &.featured {
    border: 2px solid gold;
  }

  /* Responsive adjustments */
  @media (min-width: 768px) {
    display: grid;
    grid-template-columns: 200px 1fr;

    .image {
      aspect-ratio: 1;
    }
  }
}

Browser Support

CSS nesting is well-supported in modern browsers:

  • Chrome 120+
  • Firefox 117+
  • Safari 17.2+
  • Edge 120+

For older browsers, consider using a build tool like PostCSS with the nesting plugin, or continue using Sass.

/* Feature detection */
@supports selector(&) {
  .card {
    .title {
      /* Nested styles */
    }
  }
}

Best Practices

  1. Limit nesting depth β€” Keep nesting to 2-3 levels maximum to avoid specificity issues

  2. Use & explicitly for clarity β€” Even when optional, & can make relationships clearer

  3. Group related styles β€” Put pseudo-classes and media queries near the properties they modify

  4. Don't nest everything β€” Independent components should have their own top-level selectors

  5. Consider output CSS β€” Remember that nesting expands to full selectors; deeply nested rules create long, specific selectors

Summary

  • CSS nesting lets you write child selectors inside parent selectors
  • The & nesting selector references the parent
  • At-rules like @media can be nested inside selectors
  • Keep nesting shallow (2-3 levels) to avoid specificity issues
  • Syntax is similar to Sass, making migration straightforward
  • Well-supported in modern browsers (2024+)

Native CSS nesting eliminates one of the biggest reasons to use preprocessors. It makes stylesheets more organized and maintainable while keeping everything in standard CSS.

References