Sarah Chima Atuonwu I am a Fullstack software developer that is passionate about building products that make lives better. I also love sharing what I know with others in simple and easy-to-understand articles.

Native CSS nesting: What you need to know

5 min read 1609

Native CSS Nesting

When we use a CSS preprocessor like Sass or Less, we can nest a CSS style rule within another rule to write clean and understandable code. This nesting rule is not supported yet in native CSS. At the moment, it is a working draft and only available for discussion.

However, in this tutorial, we will discuss what nesting is and how it is used in preprocessors. Then, we will discuss some of its advantages and how it will be used natively in CSS.

Here’s what we’ll cover:

What is CSS nesting?

You may be familiar with writing CSS this way:

.header {
  background-color: blue;
}

.header p {
  font-size: 16px;
}
.header p span:hover{
  color: green
} 

Consider another way of writing these same styles with CSS nesting:

.header {
  background-color: blue;
  p {
    font-size: 16px;
    span {
      &:hover {
        color: green
      }
    }
  }
}

This syntax has been possible with CSS preprocessors like Sass and Less. As you can see from both code samples above, nesting is enclosing a selector inside another selector. Nesting helps you to group related styles and write CSS in a nested hierarchy.

If you think this is different, consider that you have been doing this all along with HTML. So rather than writing the same selector over and over again to style specific children elements or pseudo-selectors, you can just nest them under a single selector.

Advantages of nesting

From the paragraph above, you can already see some advantages of using nesting. Let’s consider two more:

  1. Writing more modular and maintainable stylesheets. Rather than having the same selector in multiple places in a stylesheet, you can place all styles related to that selector in one place. This will make development time and debugging time much faster
  2. It allows you to nest media queries. With nesting, there’s no need to have a separate media query rule for a selector. You can add this right where you defined the selector

The question now is, “How can we use CSS nesting in native CSS?”

How to nest selectors in CSS

Nesting in native CSS is pretty much the same as we saw in the example above. However, in native, we must begin each nested selector with a “nesting selector” syntax, &, or we use the @nest rule.

If we rewrite the CSS code above with native CSS nesting, we will have the following:

.header {
  background-color: blue;
  & p {
    font-size: 16px;
    & span {
      &:hover {
        color: green
      }
    }
  }
}

As mentioned earlier, the & is needed at the beginning of each selector for the nesting to be valid. We can think of the & as referencing the parent selector. That way, if we invert the above CSS style and replace every & with its parent selector, we will get back the initial CSS structure.

Similar to the above type selector, we can as well nest class selectors.

Nesting CSS classes

Let’s take another example. If we want to write compound selectors like the one below using native CSS nesting:

h1.header {
  font-weight: 700
}

We do this:

h1 {
  &.header {
    font-weight: 700
  }
}

As we can see, replacing the & with the parent selector, h1, gives us back the h1.header. Whenever we add a selector (in this case, a class selector) on the same element, we must ignore the space between & and the selector.

Let’s take another example. Let’s rewrite the following style rules using nested classes:

.foo { color: #000000; }
.foo .bar { color: #fd2020; }
.foo .bar > .baz { color: #ffffff; }

For the first nested class selector, we will have:

.foo {
  color: #000000;
  & .bar {
    color: #fd2020; 
  }
}

And then, adding the second nested class gives us:

.foo {
  color: #000000;
  & .bar {
    color: #fd2020; 
    & > .baz {
      color: #ffffff;
    } 
  }
}

The same method applies to nesting conditional rules such as media queries. Take a look at the following nested rules:

.header {
  font-size: 40px

  @media (max-width: 760px ) {
    & {
      font-size: 24px;
    }
  }
}

We can see that by replacing the & with the parent selector, .header, we will get the following equivalent:

.header {
  font-size: 40px;
}
 
@media (max-width: 760px) {
  .header {
    font-size: 24px;
  }
}

As we can see, CSS nesting helps in grouping similar styles together.



Grouping CSS selectors

Imagine we have a group of selectors like so:

#header span,
#header a,
p span,
p a {
  color: #0000ff;
}

First, with the CSS is() function, we can make the style rules a bit compact, like so:

:is(#header, p) span,
:is(#header, p) a {
    color: #ff0000;
}

Then, to create nested style rules, we must begin each selector with &. In this case, we will have the following:

#header, p {
    & span, & a { color: #ff0000; }
}

It doesn’t get simpler than this.

The @nest rule

While direct nesting with & may seem perfect, there are some valid nesting selectors that it fails to handle. Let’s consider an example:

.header {
  background-color: white;
  .dark & {
    background-color: blue; 
 }
}

The above nesting is valid for nesting selectors. But direct nesting in CSS is not able to handle it. This is where the @nest rule comes in.

The @nest rule allows nesting to be more flexible in CSS. So rather than having the nesting selector only at the beginning of the nested selector, the @nest rule allows nesting to be less constrained as long as there’s a nesting selector(&) in the nested selector.

The example above can be fixed using the @nest rule:

.header {
  background-color: white;
  @nest .dark & {
    background-color: blue; 
 }
}

This is equivalent to:

.header {
  background-color: white;
}

.dark .header {
    background-color: blue; 
}

Another example:

.header {
  background-color: white;
  @nest :not(&) {
    background-color: blue;
  }
}

This is equivalent to:

.header {
  background-color: white;
}

:not(.header) {
  background-color: blue;
}

Note that for the @nest rule to be valid, the direct nesting selector (&) must be present in the nested selector, or else it will be invalid. So the example below is not valid because there’s no nesting selector present:

.header {
  background-color: white;
  @nest .dark {
    background-color: blue; 
 }
}

N.B., CSS is not supported by all browsers now, but you can use PostCSS to convert your nested CSS to valid CSS that is understood by browsers.

CSS nesting and specificity

In CSS, specificity is a set of rules that determine which styles are applied to an element. If two or more selectors apply to the same element, the one with the highest specificity is applied. For instance, consider the example below:

<html>
  <h1 class="header" id="header" />
</html&gt;

<style>
  #header {
    color: red;
  }
  .header {
    color: blue;
  }

  h1 {
    color: green;
  }
</style>

CSS cascades, so ideally the last selector applied in the stylesheet (h1) should be applied.
However, the color of the H1 element will be red, because the id selector(#header) has a higher specificity. For a more detailed explanation of specificity, you can read this detailed article on specificity.

How is this relevant to CSS nesting? Let’s take a look at what goes on in the browser when we use nesting. Consider this example:

#header, p {
  & span {
    color: red;
  }
}

This is equivalent to:

:is(#header, p) span { color: red }

The :is selector uses the highest specificity among the selector for all the selectors, so it will be difficult to override the styles. If you have a p element you want to change the color of the span, for example, like this below:

<p class="paragraph">
  <span>hey there <span>
</p>

<style>
  #header, p {
    & span {
      color: red;
    }
  }
 .paragraph {
    & span {
      color: green;
    }
  }
</style>

The color red will still be applied because the specificity of the #header is higher than that of the class .paragraph.

This is important to avoid resorting to very complex selectors or the use of !important to overwrite styles. Next, let’s consider some general guidelines to follow with nesting.

Common guides in using nesting

Avoid over-nesting

Since nesting makes it easy to nest styles, you may be tempted to over-nest selectors. Consider the example below:

main {
  & section {
    background-color: red;
    & ul {
        background-color: green;
        & .list {
          font-size: 16px;
          & .link {
            color: pink;
              & :hover {
                color: blue
              }
          }
        }
      }
  }
}

This is equivalent to

main section {
  background-color: red;
}

main section ul {
  background-color: green;
}

main section ul .list{
  font-size: 16px;
}

main section ul .list .link{
  color: pink;
}

main section ul .list .link:hover{
  color: blue;
}

The last selector has six levels of nesting. This can lead to a lot of specificity issues if you ever want to override the styles. As a general guide, keep nesting only three levels deep. You can use stylelint to keep this in check. Prefer to use classes with descriptive names as much as possible.

Styles after nested selectors are ignored

This is good to keep in mind when using nesting. Consider this example:

main {
  & section {
    background-color: red;
  }
  color: green;
}

The color: green will be ignored since it is after the nested selector. With CSS nesting, any styles should be applied before the nested selectors. This is one difference between CSS nesting and nesting with other preprocessors.

Conclusion

Nesting is an exciting feature that will soon be added to native CSS. In this article, we have discussed the following:

  • Nesting will help to keep stylesheets modular and more maintainable. This is because with nesting, all styles related to a selector, children/parent selector, and even media queries can be nested in the same place
  • How and when you can use direct nesting and the @nest rule
  • Why you should keep specificity in mind while using nesting and avoid over-nesting

For further study on CSS nesting, you can refer to the W3C draft for the CSS nesting module.

Is your frontend hogging your users' CPU?

As web frontends get increasingly complex, resource-greedy features demand more and more from the browser. If you’re interested in monitoring and tracking client-side CPU usage, memory usage, and more for all of your users in production, try LogRocket.https://logrocket.com/signup/

LogRocket is like a DVR for web and mobile apps, recording everything that happens in your web app, mobile app, or website. Instead of guessing why problems happen, you can aggregate and report on key frontend performance metrics, replay user sessions along with application state, log network requests, and automatically surface all errors.

Modernize how you debug web and mobile apps — .

Sarah Chima Atuonwu I am a Fullstack software developer that is passionate about building products that make lives better. I also love sharing what I know with others in simple and easy-to-understand articles.

5 Replies to “Native CSS nesting: What you need to know”

  1. Maybe not best to use it unless totally necessary, as SCSS still has advantages of not requiring to use that many ampersands in the code. And it can easily be forgotten or that it can catch errors before the compiling has completed. And also that we have modules that we can work from which would make it ideal. But either way would be good to have that as native.

    Kind regards,
    Michael

  2. BBEdit can reformat these before and after examples to be much easier to understand. For instance, here’s the over-nested example:

    main
    {
    & section { background-color: red;
    & ul { background-color: green;
    & .list { font-size: 16px;
    & .link { color: pink;
    &: hover { color: blue;
    }

    main section { background-color: red; }
    main section ul { background-color: green; }
    main section ul .list { font-size: 16px; }
    main section ul .list .link { color: pink; }
    main section ul .list .link:hover { color: blue; }

  3. After writing Less, Sass, SCSS, Stylus, back to SCSS… and now spending a year with no pre-processor: it’s hard to imagine using this syntax. As huge fans of nesting… we can’t believe we’re come to a point where we might just prefer not to. If we could skip the & on every line, and we also had HTTP2 or something concatenate the files natively, maybe it would be a winner. We’ll cross our fingers for something better to happen… or wait to evolve our stance.

Leave a Reply