An Introduction to Native CSS Nesting

Share this article

An Introduction to Native CSS Nesting

Nesting is one of the core reasons for using a CSS preprocessor such as Sass. The feature has now arrived in standard browser CSS with a similar syntax. Can you drop the preprocessor dependency from your build system?

CSS nesting saves typing time and can make the syntax easier to read and maintain. Up till now, you’ve had to type full selector paths like this:

.parent1 .child1,
.parent2 .child1 {
  color: red;
}

.parent1 .child2,
.parent2 .child2 {
  color: green;
}

.parent1 .child2:hover,
.parent2 .child2:hover {
  color: blue;
}

Now, you can nest child selectors inside their parent, like so:

.parent1, .parent2 {

  .child1 {
    color: red;
  }

  .child2 {
    color: green;

    &:hover {
      color: blue;
    }
  }

}

You can nest selectors as deep as you like, but be wary about going beyond two or three levels. There’s no technical limit to the nesting depth, but it can make code harder to read and the resulting CSS may become unnecessarily verbose.

Until April 2023, no browser understood the nested CSS syntax. You required a build step with a CSS preprocessor such as Sass, Less, or PostCSS to transform the nested code to the regular full-selector syntax. Nesting has now arrived in Chrome 112+ and Safari 16.5+, with Firefox support coming later in 2023 (version 115 has it available behind a feature flag).

Data on support for the css-nesting feature across the major browsers from caniuse.com

Can you drop your preprocessor in favor of native nesting? As usual … it depends. Native nesting syntax has evolved over the past couple of years. It’s superficially similar to Sass — which will please most web developers — but don’t expect all SCSS code to work directly as you expect.

Native CSS Nesting Rules

You can nest any selector inside another, but it must start with a symbol such as &, . (for a HTML class), # (for a HTML id), @ (for a media query), :, ::, *, +, ~, >, or [. In other words, it cannot be a direct reference to an HTML element. This code is invalid and the <p> selector is not parsed:

.parent1 {

  /* FAILS */
  p {
    color: blue;
  }

}

The easiest way to fix this is to use an ampersand (&), which references the current selector in an identical way to Sass:

.parent1 {

  /* WORKS */
  & p {
    color: blue;
  }

}

Alternatively, you could use one of these:

  • > p — but this would style direct children of .parent1 only

  • :is(p) — but :is() uses the specificity of the most specific selector

  • :where(p) — but :where() has zero specificity

They would all work in this simple example, but you could encounter specificity issues later with more complex stylesheets.

The & also allows you to target pseudo-elements and pseudo-classes on the parent selector. For example:

p.my-element {

  &::after {}

  &:hover {}

  &:target {}

}

If you don’t use &, you’ll be targeting all child elements of the selector and not p.my-element itself. (The same would occur in Sass.)

Note that you can use an & anywhere in the selector. For example:

.child1 {

  .parent3 & {
    color: red;
  }

}

This translates to the following non-nested syntax:

.parent3 .child1 { color: red; }

You can even use multiple & symbols in a selector:

ul {

  & li & {
    color: blue;
  }

}

This would target nested <ul> elements (ul li ul), but I’d recommend against using this if you want to keep your sanity!

Finally, you can nest media queries. The following code applies a cyan color to paragraph elements — unless the browser width is at least 800px, when they become purple:

p {

  color: cyan;

  @media (min-width: 800px) {
    color: purple;
  }

}

Native CSS Nesting Gotchas

Native nesting wraps parent selectors in :is(), and this can lead to differences with Sass output.

Consider the following nested code:

.parent1, #parent2 {
  .child1 {}
}

This effectively becomes the following when it’s parsed in a browser:

:is(.parent1, #parent2) .child1 {}

A .child1 element inside .parent1 has a specificity of 101, because :is() uses the specificity of its most specific selector — in this case, the #parent2 ID.

Sass compiles the same code to this:

.parent1 .child1,
#parent2 .child1 {
}

In this case, a .child1 element inside .parent1 has a specificity of 002, because it matches the two classes (#parent2 is ignored). Its selector is less specific than the native option and has a greater chance of being overridden in the cascade.

You may also encounter a subtler issue. Consider this:

.parent .child {

  .grandparent & {}

}

The native CSS equivalent is:

.grandparent :is(.parent .child) {}

This matches the following mis-ordered HTML elements:

<div class="parent">
  <div class="grandparent">
    <div class="child">MATCH</div>
  </div>
</div>

MATCH becomes styled because the CSS parser does the following:

  1. It finds all elements with a class of child which also have an ancestor of parentat any point up the DOM hierarchy.

  2. Having found the element containing MATCH, the parser checks whether it has an ancestor of grandparent — again, at any point up the DOM hierarchy. It finds one and styles the element accordingly.

This is not the case in Sass, which compiles to this:

.grandparent .parent .child {}

The HTML above is not styled, because the element classes don’t follow a strict grandparent, parent, and child order.

Finally, Sass uses string replacement, so declarations such as the following are valid and match any element with an outer-space class:

.outer {
  &-space { color: black; }
}

Native CSS ignores the &-space selector.

Do You Still Require a CSS Preprocessor?

In the short term, your existing CSS preprocessor remains essential. Native nesting is not supported in Firefox or Chrome/Safari-based browsers, which have not received updates in a few months.

The Sass development team has announced they will support native CSS nesting in .css files and output the code as is. They will continue to compile nested SCSS code as before to avoid breaking existing codebases, but will start to emit :is() selectors when global browser support reaches 98%.

I suspect preprocessors such as PostCSS plugins will expand nested code for now but remove the feature as browser support becomes more widespread.

Of course, there are other good reasons to use a preprocessor — such as bundling partials into a single file and minifying code. But if nesting is the only feature you require, you could certainly consider native CSS for smaller projects.

Summary

CSS nesting is one of the most useful and practical preprocessor features. The browser vendors worked hard to create a native CSS version which is similar enough to please web developers. There are subtle differences, though, and you may encounter unusual specificity issues with (overly) complex selectors, but few codebases will require a radical overhaul.

Native nesting may make you reconsider your need for a CSS preprocessor, but they continue to offer other benefits. Sass and similar tools remain an essential part of most developer toolkits.

To delve more into native CSS nesting, check out the W3C CSS Nesting Specification.

Frequently Asked Questions (FAQs) on Native CSS Nesting

What is the significance of native CSS nesting in web development?

Native CSS nesting is a crucial aspect of web development as it allows developers to nest one style rule within another. This feature enhances readability and maintainability of CSS code by keeping related rules together. It also reduces the amount of code written, making the process more efficient. Native CSS nesting is especially beneficial in large projects where CSS can become complex and difficult to manage.

How does native CSS nesting differ from preprocessor nesting?

While both native CSS nesting and preprocessor nesting allow for nesting of style rules, they differ in their implementation. Preprocessor nesting, as seen in preprocessors like Sass and Less, is a feature that has been around for a while. However, native CSS nesting is a newer feature that is built directly into CSS. This means that it doesn’t require a preprocessor to work, making it more accessible and easier to use for developers who don’t use preprocessors.

Is native CSS nesting supported by all browsers?

As of now, native CSS nesting is not supported by all browsers. It is currently a working draft in the CSS specification and is only supported in Firefox behind a flag. However, it is expected to gain wider support in the future as it becomes more standardized. You can check the current level of support on websites like Can I Use.

How can I use native CSS nesting in my projects?

To use native CSS nesting, you simply nest one style rule within another using the ‘&’ character. The nested rule will then apply to the element selected by the outer rule. For example, if you want to style a paragraph within a div, you would write it as follows:

div {
& p {
color: blue;
}
}

Can I nest media queries with native CSS nesting?

Yes, you can nest media queries using native CSS nesting. This allows you to keep media queries related to a specific rule within that rule, enhancing the organization and readability of your code. Here’s an example:

div {
color: blue;

@media (min-width: 600px) {
& {
color: red;
}
}
}

What are the best practices for using native CSS nesting?

When using native CSS nesting, it’s important to avoid overly deep nesting as it can make your code harder to read and maintain. It’s also recommended to keep related rules together to enhance the organization of your code. Additionally, be aware of the browser support for native CSS nesting and consider using a fallback for browsers that don’t support it.

Can I use native CSS nesting with CSS frameworks?

Yes, you can use native CSS nesting with CSS frameworks. However, keep in mind that the framework must support native CSS nesting for it to work. As native CSS nesting becomes more standardized, it’s expected that more frameworks will add support for it.

How does native CSS nesting affect the specificity of selectors?

Native CSS nesting doesn’t affect the specificity of selectors. The specificity is determined by the final, fully resolved selector. This means that a nested selector has the same specificity as if it was written out in full without nesting.

Can I combine native CSS nesting with other CSS features?

Yes, you can combine native CSS nesting with other CSS features like variables, custom properties, and functions. This allows you to write more powerful and flexible styles.

What is the future of native CSS nesting?

While native CSS nesting is currently a working draft in the CSS specification, it’s expected to become a standard feature in the future. As it becomes more standardized, it’s likely to gain wider browser support and be integrated into more tools and frameworks. This makes it a valuable skill for web developers to learn and use.

Craig BucklerCraig Buckler
View Author

Craig is a freelance UK web consultant who built his first page for IE2.0 in 1995. Since that time he's been advocating standards, accessibility, and best-practice HTML5 techniques. He's created enterprise specifications, websites and online applications for companies and organisations including the UK Parliament, the European Parliament, the Department of Energy & Climate Change, Microsoft, and more. He's written more than 1,000 articles for SitePoint and you can find him @craigbuckler.

nestingselector nesting
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week