Native CSS Nesting: A Primer
Cross-posted to webinista.com. Please link to that version.
UPDATE April 9, 2023: CSS Nesting is now available in stable versions of Google Chrome and Microsoft Edge. Firefox support is still a question mark at this point. If you'd like to use CSS Nesting today, consider using the graceful degradation technique described at the end of this piece.
Pre-processors such as Sass and Less introduced nesting to CSS development. Nesting refers to the ability to group related CSS rules inside of a parent ruleset. It speeds the process of writing CSS because you don't have to re-type selectors. It can also improve the readability and maintainability of CSS by grouping related styles.
Thanks to its popularity and convenience, nesting is now a native feature of CSS. You get one of the benefits of using Sass or Less without the need for an external library and a build process.
CSS Nesting isn't yet ready for prime-time, though. Support is currently limited to Google Chrome and Microsoft Edge (versions 112 and later), and Safari Technology Preview.
CSS Nesting syntax resembles that of Sass/SCSS and Less, but its grammar has some significant differences. I’ll cover some of those differences in this piece. I'm also assuming that you're familiar with Less or Sass/SCSS and CSS.
Nesting syntax basics
As with SCSS and Less, nested rulesets are contained within a parent declaration block. However, CSS Nesting requires that nested selectors begin with a relative selector or the nesting selector (&
). Otherwise, the nested rule is ignored.
In example 1, the nested img
ruleset is invalid CSS. This syntax is perfectly fine for pre-processors. Less and Sass both transform that selector to .parent img
. This syntax does not work for native nesting.
Relative selectors
Type or element selectors are not relative selectors. That's why the img
ruleset from example 1 fails.
Relative selectors are a category of CSS selector. They include:
- id selectors such as
#primary
; - class selectors, e.g.
.media__object
; - attribute selectors, such as
[alt]
or[rel="noopener"]
; - pseudo-class selectors like
:hover
,:focus
, or:disabled
; - pseudo-elements such as
::before
and::after
; - and selectors that begin with a combinator, such as
> img
or+ p
.
To match type elements in a nested rule you can either:
- Use a combinator.
- Prefix it with the nesting selector.
- Use the
:is()
pseudo-class.
Here's a rewritten version of the nested img
ruleset from example 1.
Changing img
to & img
or :is(img)
makes the ruleset valid.
Invalid nested rules and their parent rules
Although browsers ignore invalid nested rules, an invalid nested rule does not invalidate its parent ruleset. In this case, elements that match .parent
will still have red text. Invalid rules can, however, prevent subsequent nested rules and declarations from being applied. Sibling rulesets or declaration added after the img
ruleset would bZ ignored.
Using the nesting selector multiple times
The &
nesting selector works a bit like a variable or placeholder for the selector of its parent rule. You can use the nesting selector anywhere in a selector list, and you can use it more than once. Example 2 demonstrates how to style nested unordered lists using the nesting selector.
Note the use of & &
with a space — the descendant combinator — between each ampersand. It's the equivalent of ul ul
.
Limitations
CSS Nesting is not a drop-in replacement for pre-processors. Sass/SCSS, for example, uses the &
as a concatenation operator within nested rulesets (example 4).
When compiled, Sass converts SCSS to valid CSS (example 5).
CSS Nesting does not support this at all. You need to type the full .accordion__trigger
selector, whether or not you nest those rulesets.
You also can't use the nesting selector to represent pseudo-elements. The following CSS does not work.
Nesting requires that the resulting selector is a valid one. Note that pre-processors still compile this to CSS. In both cases, however, browsers will ignore the rule set. After all, .subhead::before:hover
is not a valid selector.
Nesting multiple levels
The CSS Nesting specification does not specify a maximum nesting level. In the example that follows, rules are nested four levels deep.
Unlike pre-processors, nested native CSS does not result in larger CSS files. You may, however, choose to limit nesting depth to maximize the reusability and readability of your CSS.
Specificity
Nesting alone does not increase the specificity of a selector compared to its un-nested equivalent. In other words, the rulesets below have the same specificity.
The more you nest, however, the more specific your selectors become. Highly-specific selectors can make it difficult to reuse existing CSS to create new layouts and variations of components.
Order of appearance
Nested rules are always handled as though they occur after the parent rule, even if that's not how they're ordered in the source. Consider example 9.
It's the equivalent of the CSS shown in example 10.
For the sake of readability, group your parent rule's declarations together, at the top of the declaration block.
Nesting @
-rules
Yes, you can also nest at-rules within a ruleset. This includes conditional at-rules, such as @media
, and @supports
. It also includes the @container
and @layer
rules. The syntax is nearly identical to the way it's done in SCSS and Less.
The preceeding CSS equivalent to the CSS in example 12.
When parsed, the at-rule is applied as though it follows its parent rule. Specificity, cascade, and inheritance behave as you'd expect.
Testing for CSS Nesting support
You can use @supports
and its selector()
function to conditionally apply CSS in browsers that support nesting.
Since the fallback for nested CSS is to use un-nested CSS, though, this does not make much sense. You'd end up sending about twice as much CSS over the network.
An alternative is to use the CSS Object Model and the supports()
function to conditionally load a style sheet that contains nesting if the browser supports CSS Nesting, to load a Sass- or Less-compiled style sheet if the browser does not support CSS Nesting. of the style sheet if it does not.
Bear in mind, however, this technique doesn't yet work with Safari. As of this writing, Safari Technology Preview returns false
for CSS.supports('selector( & )')
.
Can I use it?
CSS Nesting is only available in the development / experimental versions of Chrome and Edge, as of version 112. and Safari Technology Preview also supports nesting. It's likely to ship in Safari 16.5. Its specification is still in flux. Behavior and syntax could change between now and when CSS Nesting ships in stable browser versions. Firefox’ development of this feature has not yet begun. Firefox which still has about 6% of desktop marketshare in the United States.
That said, it is a great time to experiment with CSS Nesting. If Sass or Less is part of your workflow, prepare your .less
and .scss
files for a shift to native nesting. Eliminate instances where you've used &
as a concatenator. Move away from variables in favor of using CSS Custom Properties. Begin rewriting and removing mixins, extends, exports, and functions.
Avoid using CSS Nesting in public-facing, production sites for now, though. Your CSS will fail for most of your audience.