CSS Nesting Is Useful When It Keeps Context Close

Native CSS nesting is available in modern browsers, but that does not make every nested selector a good idea. I use it when the nested rule only makes sense beside its parent—usually a state, pseudo-element, or media query.

If a selector reads clearly on its own, I keep it flat. That keeps specificity predictable and makes the class easier to find.

States belong with the component

Pseudo-classes, pseudo-elements, and attribute states are a natural fit:

.button {
  background: blue;
  color: white;

  &:hover {
    background: darkblue;
  }

  &::before {
    content: "→";
    margin-right: 0.5em;
  }
}

The same applies to an accessibility state:

.nav-item {
  color: #666;

  &[aria-current="page"] {
    color: #000;
    font-weight: bold;
  }
}

Both nested rules describe the same component rather than a new element in its DOM tree.

Media queries can stay beside the rule

For a small component, nesting its responsive change avoids splitting one idea across the stylesheet:

.header {
  padding: 1rem;
  font-size: 1.2rem;

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

Parent-dependent themes can also be readable when the relationship is shallow:

.card {
  background: white;
  
  .theme-dark & {
    background: #222;
  }
}

Keep component classes flat

I would not use nesting just to construct a BEM class:

.card {
  &__title {
    font-size: 1.5rem;
  }
}

Write the class directly instead:

.card__title {
  font-size: 1.5rem;
}

Generic descendant selectors are another warning sign:

.card {
  heading-section {
    margin-bottom: 1rem;
  }
}

Prefer an explicit component class:

.card__heading {
  margin-bottom: 1rem;
}

Deep nesting has the same problem as long descendant selectors: the CSS starts depending on the exact HTML structure. A flat modifier is usually easier to reuse:

/* Better */
.button--large {
  font-size: 1.2rem;
}

/* Avoid */
.sidebar .button {
  font-size: 1.2rem;
}

A compact pattern I like

.dropdown {
  position: relative;

  &[aria-expanded="true"] {
    /* ... */
  }

  &:hover {
    /* ... */
  }

  @media (min-width: 768px) {
    /* ... */
  }
}

This keeps the dropdown’s states and breakpoint close without encoding a chain of child elements. One nesting level is not a browser rule, but it is a useful default for humans.

Nesting is syntax, not architecture. It makes good component boundaries more compact and bad selector hierarchies easier to write. If I have to trace several levels to understand the final selector, I flatten it.