Nesting

Nesting CSS style rules can make your stylesheets more organized, easier to read, and more maintainable.

Overview

Now that you've learned about selectors, you're probably wondering about a way to better organize them in your stylesheets. Imagine you were applying styles to items inside of a "feature" section on your site. With nesting, you can group these styles inside of the .feature rule like this:

.feature {
  button {
    color: blue;
  }

  .link {
    color: red;
  }

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

This would be the same as writing each style separately:

.feature button {
  color: blue;
}

.feature .link {
   color: red;
}

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

Nesting can go as many layers deep as needed.

.feature {
  .heading {
    color: blue;

    a {
      color: green;
    }
  }
}

Grouping and establishing relationships

Nesting lets you more succinctly group and establish relationships between style rules.

The nested rule will by default be related to the outer rule as a descendant combinator. Use selectors on the nested rules to change the relationships.

/* 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 & selector

You can also use the & selector to be more explicit when nesting style rules. Think of & as a symbol representing the parent selector.

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

This would be equivalent to writing the styles like this:

.feature button {
  color: blue;
}

When & is required

Without &, nested selectors will be descendent selectors of the parent selector. To form compound selectors, & is required.

.feature {
  &:last-child {
    /* Selects the .feature element that is the :last-child, equivalent to .feature:last-child */
  }
   
  & :last-child {
    /* Selects the :last-child inside of a .feature element, equivalent to .feature :last-child */
  }

  &.highlight {
    /* Selects .feature elements that also have a .highlight class, equivalent to .feature.highlight */
  }

  & .highlight {
     /* Selects elements inside of the .feature element with the class .highlight, equivalent to .feature .highlight */
  }
}

You can also change the context and place the & selector at the end of the child selector, or on both sides of it.


/* Targets buttons with an adjacent sibling button */
button {
  & + & {
    /* … */
  }
}
img {
  .my-component & {
    /* styles for images inside of `.my-component` ... */
  }
}

In the last example, we are adding styles for images inside of an element with the .my-component class. This can be useful if you are working on a project where you can't add a class or an id to an element.

Nesting and specificity

Like :is(), the nesting selector takes the specificity of the selector with the highest specificity in the parent's selector list.

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

.intro a {
  color: blue;
}

The first rule targets all of the links inside of the #main-header and .intro elements, giving them a green color.

The second rule attempts to override this to make links inside of the .intro element blue.

We can see why this doesn't work if we look at the specificity of each rule.

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

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

Since the first rule has an id in its selector list, and nested rules take the specificity of the selector with the highest specificity, it has a higher specificity than the second rule. The links are green even for a elements that are not inside an element with the #main-header selector.

Invalid Nesting

Similar to :is(), the nesting selector cannot represent pseudo elements.

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

  & {
    border: 1px solid navy;
  }
}

You would expect the blockquote and its pseudo-elements to both have navy colored text and borders, but that's not the case. Since the & selector can't represent pseudo-elements, the nested border styles will only apply to the blockquote.

When making compound selectors using & and type selectors, the type selector must go first without any whitespace between.

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

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

This rule allows CSS nesting to work alongside pre-processing tools like Sass. In Sass, writing &p would append the parent selector to the nested type selector and the result would be .featurep.

Nesting at-rules

CSS conditional group rules like @container, @media, @supports, and @layer can also be nested.

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

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

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

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

Check your understanding

When using CSS Nesting, what does the & selector represent?

The nested child selector
Incorrect.
The parent selector
Correct!
the nearest sibling selector
Incorrect.

You can only nest two levels deep.

True
Incorrect.
False
Correct!

Which at-rules can be nested?

@media
Correct!
@container
Correct!
@import
Incorrect.
@supports
Correct!
@layer
Correct!