Editor’s note: This article was updated by Oscar Jite-Orimiono on 27 December 2023 to provide the most recent information regarding native support for CSS nesting, along with updated examples and interactive CodePen demos.
Native CSS nesting is when we nest a CSS style rule within another rule to write cleaner and more understandable code. This feature was previously completely unsupported on any browser without the help of a preprocessor like Sass or Less, but it now has partial or full support on many browsers.
In this tutorial, we will discuss what nesting is and explore some of its advantages. Then, we’ll cover how CSS nesting is used in preprocessors and natively in CSS.
At this time, native CSS nesting has partial support on:
Partial support means the nested selectors must start with a symbol. We’ll explore more on this later on.
Meanwhile, CSS nesting is now fully supported on:
You can stay up-to-date regarding the browser compatibility of native CSS nesting on CanIUse.
CSS nesting is the ability to define the style rules of one element inside another. So rather than writing the same selector over and over again to style specific child elements or pseudo-selectors, you can just nest them under a single selector.
Nesting helps you to group related styles and write CSS in a nested hierarchy.
Consider this simple HTML page:
<body> <div class="header"> <h1>Hello!</h1> <p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Corrupti nihil, officiis aut vel. <a href="#">This is a Link</a> </p> </div> </body>
There’s a header and paragraph inside a div
with the class name, .header
. There’s also a link inside the p
element.
When styling, you may be familiar with writing CSS this way:
.header{ position: absolute; left: 25%; top: 40%; transform: translate(-13%,-42%); color: #22232e; } .header h1{ font-size: 3.5rem; } .header p{ font-size: 2rem; margin: 25px 0px 25px; } .header p a{ color: #00c2cb; }
This is valid, but consider another way of writing these same styles with CSS nesting:
.header { position: absolute; left: 25%; top: 40%; transform: translate(-13%,-42%); color: #22232e; h1 { font-size: 3.5rem; } p { font-size: 2rem; margin: 25px 0px 25px; a { color: #00c2cb; } } }
This syntax should be familiar to developers who use CSS preprocessors like Sass and SCSS. Nesting like this is now possible in native CSS without the help of preprocessors.
If you had another type of selector, like the :hover
pseudo-element, here’s how it would be nested:
.header { position: absolute; left: 25%; top: 40%; transform: translate(-13%,-42%); color: #22232e; h1 { font-size: 3.5rem; } p { font-size: 2rem; margin: 25px 0px 25px; a { color: #00c2cb; &:hover { color: #e498db; } } } }
Here’s a CodePen of the HTML page:
See the Pen Page With Sass Nesting by Oscar Jite-Orimiono (@oscar-jite)
on CodePen.
The benefits of CSS nesting include:
Now, let’s explore how we can use CSS nesting in native CSS.
Nesting in native or plain CSS is pretty much the same as we saw when using the preprocessor. However, in some browsers and with certain types of selectors, we must begin with the nesting selector, &
.
Here’s a screenshot of the page from the previous example on Opera without the nesting selector:
The style rules of the nested selectors are not applied because this browser doesn’t support nesting without &
. So, to give every user the same experience no matter which browser they use, use the ampersand symbol when nesting.
If we rewrite the CSS code from the previous example with native CSS nesting, we will have the following:
.header { position: absolute; left: 25%; top: 40%; transform: translate(-13%,-42%); color: #22232e; & h1 { font-size: 3.5rem; } & p { font-size: 2rem; margin: 25px 0px 25px; & a { color: #00c2cb; &:hover { color: #3498db; } } } }
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 the parent selector, we will get back the initial CSS structure.
Let’s take another example. This time, we’ll look at compound selectors like the one below:
h1.header { font-weight: 700 }
If we rewrite the code above using native CSS nesting, it looks like 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 by rewriting 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 the following:
.foo { color: #000000; & .bar { color: #fd2020; } }
Then, adding the second nested class gives us the below:
.foo { color: #000000; & .bar { color: #fd2020; & > .baz { color: #ffffff; } } }
Another interesting thing about nesting class selectors is that you don’t need &
because class names start with the period symbol when you declare them in CSS.
So, if we rewrote the HTML for our page to include classes, like so:
<body> <div class="header"> <h1 class="heading">Hello!</h1> <p class="text">Lorem ipsum dolor sit, amet consectetur adipisicing elit. Corrupti nihil, officiis aut vel. <a href="#" class="link">This is a Link</a> </p> </div> </body>
Then we could nest the class selectors without the ampersand:
.header { position: absolute; left: 25%; top: 40%; transform: translate(-13%, -42%); color: #22232e; .heading { font-size: 3.5rem; } .text { font-size: 2rem; margin: 25px 0px 25px; .link { color: #00c2cb; &:hover { color: #3498db; } } } }
The above will work fine on every browser, even without the ampersand. This also applies to other types of selectors: the style rules for the nested selectors are valid as long as they start with the appropriate symbol.
Other selector symbols include #
for ids, :
and ::
for pseudo elements and classes, the *
universal selector, sibling selectors +
and ~
, the >
child selector, and finally, the attribute selector []
.
The method we explored above also applies to nesting conditional rules such as media queries and feature queries (@supports
). Take a look at the following nested rules:
.header { font-size: 40px @media (max-width: 760px ) { & { font-size: 24px; } } }
We can see that replacing the &
with the parent selector, .header
, gives us the following equivalent:
.header { font-size: 40px; } @media (max-width: 760px) { .header { font-size: 24px; } }
You can also have more than one nested conditional rule:
.header { font-size: 40px @media (orientation: landscape) { & { grid-auto-flow: column; } @media (max-width: 760px ) { & { font-size: 24px; } } }
Other at-rules include @scope
, @layer
, and @container
.
Imagine we have a group of selectors such as the following:
#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.
In CSS, specificity describes 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. CSS cascades, so under normal circumstances, the last selector declared in the stylesheet should have more priority.
How is this relevant to CSS nesting? Consider this example of some simple page content with headers:
<body> <section id="header"> <div> <h1 class="heading"> This is a h1 </h1> </div> </section> <section class="text"> <div> <h2>This is a h2</h2> </div> </section> </body>
Now, say you want to style the headers using nesting:
#header, .text{ background: #22323e; & h1, & h2 { color: white; } }
Here’s what you should see:
See the Pen Native CSS Nesting And Specificity Demo by Oscar Jite-Orimiono (@oscar-jite)
on CodePen.
Both headings have the color:
white
style applied. However, note that the h1
has a class name of heading
. Imagine that later on in your stylesheet, you decide to use this class name like so:
#header, .text{ background: #22323e; & h1, & h2 { color: white; } } .heading{ color: red; }
Despite this addition to your stylesheet, nothing will happen — both headings will remain white. This is because the equivalent of the nested rules is the following:
:is(#header, .text) h1,h2{ color: white; }
The :is
selector takes the specificity of the most specific element, which in this case is the ID selector #header
. Its style rules will have higher priority, making them difficult to overwrite later on.
Understanding this behavior is important, as it prevents the need for overly complex selectors or the use of !important
to overwrite styles.
So far, we’ve explored in detail how to achieve nesting with a preprocessor as well as native CSS nesting. Now, let’s consider some general rules to follow when nesting style rules.
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 the following code:
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.
A common rule of thumb is to keep nesting only three levels deep. You can use Stylelint to keep your nesting in check. Use classes with descriptive names as much as possible.
When nesting styles in CSS, any styles you define after nested selectors will be ignored. This is good to keep in mind, as it emphasizes the importance of organizing your rules carefully. Consider this example:
main { & section { background-color: red; } color: green; }
The color: green
will be ignored since it is written after the nested selector. With CSS nesting, any style rules for the parent element should be declared before the nested selectors. This is one difference between native CSS nesting and nesting with preprocessors.
Nesting is an exciting feature in native CSS that helps keep stylesheets modular and more maintainable. With nesting, all styles related to a selector, children/parent selector, and even media queries can be nested in the same place.
In this article, we explored how nesting works in CSS and why it’s important in modern frontend development, exploring examples with a preprocessor and with native CSS nesting. We also discussed why you should avoid over-nesting and keep specificity in mind while using nesting.
For further study on CSS nesting, you can refer to the W3C draft for the CSS nesting module.
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.
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 — start monitoring for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.
6 Replies to "Native CSS nesting: What you need to know"
Finally! Writing vanilla CSS has been a pain once you’re used to SCSS.
Now that’s a LOT of ampersands…
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
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; }
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.
Hi Sarah, thanks a lot. About “Styles after nested selectors are ignored”, it works on my side so does it depends of the browser?