Lesson 4: Nesting

Nesting CSS rules makes stylesheets organized and easier to maintain.

Overview

Now that you know how selectors work, you probably want a clean way to organize them. If you are styling items inside a feature section on your site, nesting lets you group those styles inside the parent rule.

.feature {
  button {
    color: blue;
  }

  .link {
    color: red;
  }

  .text {
    font-size: 1.3em;
  }
}

This is exactly the same as writing each style separately.

.feature button {
  color: blue;
}

.feature .link {
   color: red;
}

.feature .text {
   font-size: 1.3em;
}

You can nest styles as deep as you need.

.feature {
  .heading {
    color: blue;

    a {
      color: green;
    }
  }
}

[!NOTE] Even though the browser lets you nest styles deeply, nesting too far is bad practice. It makes maintaining your CSS a headache. Try to keep nesting to two or three layers max.

Grouping and establishing relationships

Nesting helps you group related rules and show how elements connect. By default, a nested rule behaves like a descendant combinator. You can use combinators on the nested rules to change how they relate.

/* targets headings that are siblings of the .feature element and come immediately after it */
.feature {
  + .heading {
    color: blue;
  }

/* targets all paragraphs that are direct children of the .feature element */
  > p {
    font-size: 1.3em;
  }
}

Define explicit relationships with the parent selector

You can use the ampersand symbol to be explicit when nesting rules. Think of the ampersand as a placeholder for the parent selector.

.feature {
 & button {
    color: blue;
  }
}

This compiles to the same descendant selector.

.feature button {
  color: blue;
}

When the parent selector is required

Without the ampersand, nested selectors are treated as descendants. If you want to form compound selectors, you must use the ampersand.

.feature {
  &:last-child {
    /* Selects the .feature element that is the last child */
  }

  & :last-child {
    /* Selects the last child inside the .feature element */
  }

  &.highlight {
    /* Selects .feature elements that also have the .highlight class */
  }

  & .highlight {
     /* Selects elements inside .feature with the class .highlight */
  }
}

You can also place the ampersand at the end of the selector to change the context.

/* Targets buttons with an adjacent sibling button */
button {
  & + & {
    /* ... */
  }
}

img {
  .my-component & {
    /* styles for images inside .my-component */
  }
}

The second example styles images that live inside .my-component. This is handy when you cannot modify the HTML to add classes.

Nesting and specificity

Nested selectors calculate specificity based on the most specific item in the parent selector list. This is identical to how the :is() pseudo-class works.

#main-header,
.intro {
  & a {
    color: green;
  }
}

.intro a {
  color: blue;
}

The first rule targets all links inside #main-header and .intro, making them green. The second rule tries to make links inside .intro blue. It will fail.

Look at the specificity calculations to see why.

/* equivalent to :is(#main-header, .intro) a with specificity (1, 0, 1) */
#main-header,
.intro {
  & a {
    color: green;
  }
}

/* lower specificity of (0, 1, 1) */
.intro a {
  color: blue;
}

Because the first rule has an ID in its selector list, the nested rule inherits that high specificity. That overrides the simpler class selector. The links remain green even if they are only inside the .intro element.

Invalid nesting

The nesting selector cannot represent pseudo-elements.

blockquote, blockquote::before, blockquote::after {
  color: navy;

  & {
    border: 1px solid navy;
  }
}

You might expect the blockquote and its pseudo-elements to get navy borders, but they will not. The ampersand cannot stand in for pseudo-elements, so the border only applies to the blockquote.

When you create compound selectors with type selectors, the type selector must go first.

/* valid nesting */
.feature {
  p& {
    font-weight: bold;
  }
}

/* invalid nesting */
.feature {
  &p {
    font-weight: bold;
  }
}

This restriction keeps native CSS nesting compatible with Sass. In Sass, writing &p would output .featurep.

Nesting at-rules

You can nest conditional group rules like @container, @media, @supports, and @layer.

.feature {
  @media (min-width: 40em) {
    /* ... */
  }

  @container (inline-size > 900px) {
    /* ... */
  }
}

.feature {
  @supports (display: grid) {
    /* ... */
  }
}

.feature {
  @layer component {
    h2 {
      /* ... */
    }
  }
}

Adapted from Learn CSS © Google and contributors, licensed under CC BY 4.0 (prose) and Apache 2.0 (code samples).