← Back to DevBytes

Mastering CSS Nesting: Tips and Best Practices

What Is CSS Nesting?

CSS Nesting is a long-awaited feature that allows you to nest CSS selectors inside other selectors, mirroring the hierarchical structure of your HTML. Instead of repeatedly writing parent selectors, you can now group related styles together in a way that's both logical and visually intuitive. This capability was previously available only through preprocessors like Sass, SCSS, or Less, but is now supported natively in modern CSS.

At its core, CSS Nesting lets you write rules that are scoped to a parent element by simply placing them inside that parent's rule block. The browser resolves the nested selectors by implicitly combining them with the parent selector, producing the same final CSS you would have written manually—but with far less duplication.

Here's a side-by-side comparison of traditional CSS versus nested CSS for the same card component:

/* Traditional CSS — repetitive and harder to scan */
.card {
  background: white;
  border-radius: 8px;
  padding: 1.5rem;
}

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

.card .body {
  color: #555;
  line-height: 1.6;
}

.card .footer {
  border-top: 1px solid #eee;
  padding-top: 1rem;
  margin-top: 1rem;
}

.card .footer .button {
  background: #0066cc;
  color: white;
  padding: 0.5rem 1rem;
  border-radius: 4px;
}

/* CSS Nesting — hierarchical, less repetition, easier to maintain */
.card {
  background: white;
  border-radius: 8px;
  padding: 1.5rem;

  .title {
    font-size: 1.25rem;
    font-weight: 600;
    margin-bottom: 0.5rem;
  }

  .body {
    color: #555;
    line-height: 1.6;
  }

  .footer {
    border-top: 1px solid #eee;
    padding-top: 1rem;
    margin-top: 1rem;

    .button {
      background: #0066cc;
      color: white;
      padding: 0.5rem 1rem;
      border-radius: 4px;
    }
  }
}

The nested version mirrors your HTML structure: a .card contains a .title, .body, and .footer, which in turn contains a .button. This visual hierarchy makes it immediately obvious which elements belong to which parent, reducing the cognitive load when scanning stylesheets.

Why CSS Nesting Matters

🚀 Deploy your AI agent in 10 minutes

Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.

Try it free →

CSS Nesting isn't just syntactic sugar—it fundamentally improves how you author, read, and maintain stylesheets. Here's why it matters:

Browser support for CSS Nesting is now widespread. As of 2024, it's supported in Chrome (105+), Edge (105+), Firefox (117+), and Safari (16.5+). For older browsers, you can use a transpiler like PostCSS with the postcss-nesting plugin, which converts nested CSS into flat selectors automatically.

How to Use CSS Nesting

Basic Nesting Syntax

The simplest form of nesting is placing a child selector directly inside a parent's rule block. The browser automatically combines them with a descendant combinator (a space), just like writing .parent .child in traditional CSS:

/* A navigation component with nested children */
.navbar {
  display: flex;
  align-items: center;
  background: #1a1a2e;
  padding: 0 1rem;

  .nav-list {
    display: flex;
    list-style: none;
    margin: 0;
    padding: 0;
    gap: 0.5rem;
  }

  .nav-item {
    position: relative;

    .nav-link {
      display: block;
      padding: 0.75rem 1rem;
      color: #e0e0e0;
      text-decoration: none;
      transition: color 0.2s ease;
    }
  }
}

This compiles to the equivalent of:

.navbar { display: flex; align-items: center; background: #1a1a2e; padding: 0 1rem; }
.navbar .nav-list { display: flex; list-style: none; margin: 0; padding: 0; gap: 0.5rem; }
.navbar .nav-item { position: relative; }
.navbar .nav-item .nav-link { display: block; padding: 0.75rem 1rem; color: #e0e0e0; text-decoration: none; transition: color 0.2s ease; }

The Ampersand (&) Symbol

The & symbol is the key to unlocking advanced nesting patterns. It represents the parent selector in the current nesting context and gives you precise control over how selectors are combined. Without &, nested selectors are treated as descendants. With &, you can create compound selectors, attach pseudo-classes to the parent, or build complex relationships.

Here are the most common & use cases:

/* Using & for pseudo-classes on the parent element */
.button {
  background: #0066cc;
  color: white;
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  transition: background 0.2s;

  /* & replaces .button, producing .button:hover */
  &:hover {
    background: #0052a3;
  }

  /* Produces .button:focus-visible */
  &:focus-visible {
    outline: 3px solid #0066cc;
    outline-offset: 2px;
  }

  /* Produces .button:active */
  &:active {
    transform: scale(0.97);
  }

  /* Produces .button:disabled */
  &:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
}

The ampersand also lets you create compound class selectors and handle element state combinations elegantly:

/* Compound selectors and state variants */
.card {
  background: white;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  overflow: hidden;

  /* Produces .card.featured — a card that also has the .featured class */
  &.featured {
    border-color: #ffd700;
    box-shadow: 0 0 20px rgba(255, 215, 0, 0.3);
  }

  /* Produces .card.promotional */
  &.promotional {
    background: linear-gradient(135deg, #667eea, #764ba2);
    color: white;
    border-color: transparent;
  }

  /* Nested inside featured: produces .card.featured .badge */
  &.featured {
    .badge {
      background: #ffd700;
      color: #1a1a2e;
      font-weight: 600;
    }
  }
}

Nesting Pseudo-Classes and Pseudo-Elements

Pseudo-classes and pseudo-elements integrate seamlessly with nesting. You can attach them directly to the parent using &, or nest them as children when they apply to descendant elements:

/* Pseudo-elements with nesting */
.quote {
  font-style: italic;
  position: relative;
  padding-left: 2rem;
  color: #444;

  /* Produces .quote::before */
  &::before {
    content: '"';
    position: absolute;
    left: 0;
    top: 0;
    font-size: 3rem;
    color: #ccc;
    line-height: 1;
  }

  /* Produces .quote::after */
  &::after {
    content: '"';
    font-size: 3rem;
    color: #ccc;
    vertical-align: bottom;
  }
}

/* Nesting pseudo-classes on child elements */
.link-list {
  list-style: none;
  padding: 0;

  .link-item {
    margin-bottom: 0.5rem;

    /* Produces .link-list .link-item:last-child */
    &:last-child {
      margin-bottom: 0;
    }

    /* Produces .link-list .link-item:nth-child(odd) */
    &:nth-child(odd) {
      background: #f9f9f9;
    }
  }

  .link-anchor {
    color: #0066cc;
    text-decoration: none;

    /* Produces .link-list .link-anchor:visited */
    &:visited {
      color: #551a8b;
    }

    /* Produces .link-list .link-anchor:hover */
    &:hover {
      text-decoration: underline;
      color: #004499;
    }
  }
}

Nesting Combinators

CSS Nesting supports all standard combinators—descendant (space), child (>), adjacent sibling (+), and general sibling (~). You can use them explicitly for more precise selector relationships:

/* Using combinators inside nested rules */
.form-group {
  margin-bottom: 1.5rem;

  /* Direct child combinator: .form-group > .form-label */
  & > .form-label {
    display: block;
    font-weight: 600;
    margin-bottom: 0.25rem;
  }

  /* Direct child combinator: .form-group > .form-input */
  & > .form-input {
    width: 100%;
    padding: 0.75rem;
    border: 2px solid #ddd;
    border-radius: 6px;
    font-size: 1rem;

    /* Produces .form-group > .form-input:focus */
    &:focus {
      border-color: #0066cc;
      outline: none;
      box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.2);
    }
  }

  /* Adjacent sibling: .form-group > .form-label + .form-input */
  & > .form-label + .form-input {
    margin-top: 0.5rem;
  }
}

Nesting Media Queries and Container Queries

One of the most powerful aspects of CSS Nesting is the ability to place media queries inside selector blocks. This keeps responsive styles co-located with the component they affect, rather than scattered throughout the stylesheet in separate @media blocks:

/* Responsive styles nested inside their component */
.product-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.5rem;
  padding: 2rem;

  /* Tablet breakpoint — scoped to .product-grid */
  @media (max-width: 1024px) {
    grid-template-columns: repeat(3, 1fr);
    gap: 1rem;
    padding: 1.5rem;
  }

  /* Mobile breakpoint */
  @media (max-width: 640px) {
    grid-template-columns: repeat(2, 1fr);
    gap: 0.75rem;
    padding: 1rem;
  }

  /* Small mobile */
  @media (max-width: 400px) {
    grid-template-columns: 1fr;
  }

  .product-card {
    border: 1px solid #eee;
    border-radius: 8px;
    overflow: hidden;
    transition: box-shadow 0.3s;

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

    /* Card-specific responsive adjustments */
    @media (max-width: 640px) {
      border-radius: 6px;

      .product-image {
        height: 150px;
      }
    }
  }
}

This pattern is transformative for large codebases. Instead of opening a global @media (max-width: 640px) { ... } block and hunting for every component that needs mobile adjustments, each component carries its own responsive rules internally. When you delete a component, its media queries disappear with it—no orphaned breakpoint code left behind.

Nesting @supports and @layer

Beyond media queries, you can nest other at-rules like @supports for feature detection and reference @layer names for cascade management:

/* Nesting @supports for progressive enhancement */
.gallery {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;

  /* Only apply if the browser supports aspect-ratio */
  @supports (aspect-ratio: 1) {
    .gallery-item {
      aspect-ratio: 16 / 9;
    }
  }

  /* Only apply if container queries are supported */
  @supports (container-type: inline-size) {
    container-type: inline-size;

    @container (min-width: 500px) {
      .gallery-item {
        flex: 1 1 calc(33.33% - 1rem);
      }
    }
  }
}

Best Practices for CSS Nesting

1. Keep Nesting Shallow

The most important rule: avoid deep nesting. While nesting is convenient, each level adds specificity and makes selectors harder to override. A good rule of thumb is to stay within 2–3 levels of nesting. Beyond that, you're likely creating fragile, overly specific selectors that will cause maintenance headaches:

/* ❌ BAD: Deep nesting — overly specific, brittle, hard to override */
.page {
  .content-area {
    .article-section {
      .paragraph-block {
        .text-span {
          .highlight-word {
            color: red;
            /* Final selector: .page .content-area .article-section
               .paragraph-block .text-span .highlight-word
               That's 6 levels — way too specific! */
          }
        }
      }
    }
  }
}

/* ✅ GOOD: Shallow nesting — clean, manageable specificity */
.page {
  .content-area {
    /* 2 levels, reasonable */
  }
}

.article-section {
  .paragraph-block {
    /* 2 levels */
  }
}

.highlight-word {
  color: red;
  /* 1 level, easily overridable */
}

2. Use & Purposefully, Not Reflexively

The & symbol is powerful but can be overused. Reserve it for cases where you genuinely need to modify the parent selector relationship—pseudo-classes, compound selectors, or combinators. For straightforward descendant nesting, let the implicit behavior do the work:

/* ❌ Overusing & when it's not needed */
.card {
  & .title {
    /* Unnecessary & — implicit nesting does this already */
  }
  & .body {
    /* Same as above */
  }
  & &:hover {
    /* Confusing double & — what does this even produce? */
  }
}

/* ✅ Using & only where it adds value */
.card {
  .title {
    /* Clean implicit nesting */
  }
  .body {
    /* Clean implicit nesting */
  }
  &:hover {
    /* & needed here to attach pseudo-class to .card itself */
  }
  &.featured {
    /* & needed for compound selector .card.featured */
  }
}

3. Prefer Relative Selectors Over Absolute Paths

When nesting, lean on relative relationships rather than recreating full selector paths. This keeps your CSS portable and resilient to DOM structure changes:

/* ❌ BAD: Hard-coding deep paths defeats the purpose of nesting */
.header {
  .header-nav-list .header-nav-item .header-nav-link {
    /* This is just flat CSS shoved inside a nest — no benefit */
  }
}

/* ✅ GOOD: Let nesting express the hierarchy naturally */
.header {
  .nav-list {
    .nav-item {
      .nav-link {
        /* Each level adds one piece, stays relative */
      }
    }
  }
}

4. Nest Responsive Rules Inside Components

Co-locate media queries with the components they modify. This is arguably the single greatest benefit of native CSS nesting—it keeps responsive logic encapsulated and prevents sprawling @media sections at the bottom of your stylesheet:

/* ✅ Excellent: Responsive rules live with their component */
.hero-banner {
  display: flex;
  align-items: center;
  min-height: 400px;
  padding: 4rem;

  @media (max-width: 768px) {
    flex-direction: column;
    min-height: auto;
    padding: 2rem;
    text-align: center;
  }

  @media (max-width: 480px) {
    padding: 1rem;
  }

  .hero-title {
    font-size: 3rem;
    line-height: 1.1;

    @media (max-width: 768px) {
      font-size: 2rem;
    }

    @media (max-width: 480px) {
      font-size: 1.5rem;
    }
  }
}

5. Avoid Mixing Concerns — Keep Nesting Hierarchical

Don't nest unrelated selectors just because you can. Nesting should reflect genuine DOM hierarchy or component composition. If two elements are siblings or live in separate branches, keep their styles separate:

/* ❌ BAD: Nesting unrelated things — confusing and misleading */
.header {
  /* Header styles */
  
  .footer-link {
    /* Footer styles inside header? Makes no structural sense */
  }
}

/* ✅ GOOD: Nesting mirrors actual DOM relationships */
.header {
  /* Header styles */
  .logo { }
  .nav { }
}

.footer {
  /* Footer styles */
  .footer-link { }
}

6. Keep Selectors Readable and Debuggable

Always remember that nested CSS compiles to flat selectors in the browser. If you can't quickly determine the final compiled selector by looking at your nested code, it's probably too complex. A good test: close your eyes, visualize the full selector path, and see if it matches your intuition. If not, flatten it out:

/* ❌ Confusing: Hard to mentally compute the final selector */
.container {
  & > .sidebar:not(.collapsed) {
    & + .main-content {
      & .widget[data-active="true"] {
        /* What's the final selector here? Take a moment...
           .container > .sidebar:not(.collapsed) + .main-content .widget[data-active="true"]
           Too many combinators and conditions in one chain */
      }
    }
  }
}

/* ✅ Clear: Break complex chains into manageable pieces */
.container {
  .sidebar {
    /* Sidebar base styles */
    &:not(.collapsed) {
      /* Expanded sidebar styles */
    }
  }
  .main-content {
    /* Main content styles */
    .widget {
      /* Widget base styles */
      &[data-active="true"] {
        /* Active widget styles — much easier to trace */
      }
    }
  }
}

7. Use Nesting to Reduce, Not Increase, Specificity Battles

One hidden danger of nesting is that it quietly increases specificity. A selector nested three levels deep has the same specificity as three chained descendant selectors in flat CSS. If you find yourself writing !important overrides or creating ever-deeper nests to win specificity wars, step back and restructure:

/* ❌ Specificity escalation through nesting */
.widget {
  .widget-content {
    .widget-button {
      background: blue;
      /* Specificity: 0,0,3 — hard to override without !important */
    }
  }
}

/* Override attempt — needs higher specificity or !important */
.custom-widget .widget-content .widget-button {
  background: green !important; /* Yuck */
}

/* ✅ Better: Use flatter, lower-specificity selectors */
.widget-button {
  background: blue;
  /* Specificity: 0,0,1 — easy to override */
}

.widget-button.custom {
  background: green;
  /* Specificity: 0,0,2 — still manageable */
}

8. Leverage Nesting for Theming and Variants

Nesting shines when you need to create themed variants of components. Group all variant styles within the base component using & compound selectors:

/* Theming with nested variants */
.alert {
  padding: 1rem;
  border-radius: 6px;
  border-left: 4px solid;
  margin-bottom: 1rem;

  /* Base alert styles */
  .alert-title {
    font-weight: 600;
    margin-bottom: 0.25rem;
  }

  .alert-body {
    font-size: 0.95rem;
    line-height: 1.5;
  }

  /* Variant: Success */
  &.alert-success {
    background: #e8f5e9;
    border-left-color: #4caf50;
    color: #2e7d32;

    .alert-title {
      color: #1b5e20;
    }
  }

  /* Variant: Warning */
  &.alert-warning {
    background: #fff8e1;
    border-left-color: #ff9800;
    color: #e65100;

    .alert-title {
      color: #bf360c;
    }
  }

  /* Variant: Error */
  &.alert-error {
    background: #ffebee;
    border-left-color: #f44336;
    color: #c62828;

    .alert-title {
      color: #b71c1c;
    }
  }

  /* Variant: Info */
  &.alert-info {
    background: #e3f2fd;
    border-left-color: #2196f3;
    color: #0d47a1;

    .alert-title {
      color: #0a3d91;
    }
  }
}

This pattern keeps all alert-related styles—base and variants—in one cohesive block. Adding a new variant means inserting one &.alert-{variant} block rather than scattering styles across multiple sections of the file.

9. Know When to Go Flat

Nesting is a tool, not a mandate. Some situations are better served by flat selectors. Use flat CSS when:

/* Flat utility classes — intentionally low specificity, globally applicable */
.text-center { text-align: center; }
.text-bold { font-weight: 700; }
.mt-2 { margin-top: 0.5rem; }
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}

/* Nesting is for component-scoped styles, not global utilities */
.card {
  /* Card-specific styles make sense nested */
  .card-title { }
}

10. Test Across Browsers and Use a Fallback Strategy

While CSS Nesting is widely supported, always verify your target browser matrix. For production projects that need to support older browsers, integrate a PostCSS pipeline with the postcss-nesting plugin. This transpiles nested CSS into flat selectors automatically, letting you author with nesting while shipping compatible code:

/* Your authored nested CSS (modern and clean) */
.tabs {
  display: flex;
  border-bottom: 2px solid #eee;

  .tab {
    padding: 0.75rem 1.5rem;
    cursor: pointer;
    border-bottom: 2px solid transparent;
    margin-bottom: -2px;

    &:hover {
      background: #f5f5f5;
    }

    &.active {
      border-bottom-color: #0066cc;
      color: #0066cc;
      font-weight: 600;
    }
  }
}

/* PostCSS output (flat, universally compatible) */
.tabs {
  display: flex;
  border-bottom: 2px solid #eee;
}
.tabs .tab {
  padding: 0.75rem 1.5rem;
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -2px;
}
.tabs .tab:hover {
  background: #f5f5f5;
}
.tabs .tab.active {
  border-bottom-color: #0066cc;
  color: #0066cc;
  font-weight: 600;
}

Conclusion

CSS Nesting represents a significant evolution in how we write stylesheets. It brings the clarity and organization of preprocessor nesting directly into the browser, eliminating the need for external tools while encouraging a component-first mindset. By mirroring your DOM structure in your CSS, nesting makes styles more intuitive to read, easier to maintain, and less prone to copy-paste errors.

The keys to mastering CSS Nesting are balance and restraint. Keep your nesting shallow—two to three levels at most. Use the & symbol deliberately for pseudo-classes, compound selectors, and combinators, not as a reflex. Co-locate responsive rules inside their components to create self-contained, portable style modules. And always remember that the final compiled selector should be something you can easily reason about.

When used thoughtfully, CSS Nesting transforms stylesheets from sprawling, repetitive documents into well-organized, hierarchical representations of your UI. It's not just about writing less code—it's about writing code that tells a clearer story of how your interface is built.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles