CSS Nesting Gotchas Part One

Introduction

CSS nesting has full browser support, adoption should increase, and there will be questions about best practices etc. You may be coming from the world of preprocessors like Sass and Less, which work differently (as you will find out if you keep reading), you might be completely new to it, and you may be using it with PostCSS and Next.js or React.

What everyone has in common is that we'll see the same gotchas!

In this post, I'm focusing on one specific gotcha, which seems to have caught a few people out already (me included). I decided to deep-dive into this one, so I could better understand it and then share the deets with you all.

Invalid type selectors

Check out the following example

Code playground
.parent {
  background: aquamarine;
  padding: 1rem;

  .child {
    background: honeydew;
  }

  p {
    background: darkgoldenrod;
  }
}
Example taken from a wesbos tweet: https://twitter.com/wesbos/status/1696201171587809761

Coming from a preprocessor world, you would expect <p> to have a darkgoldenrod background.

Let's fix it according to the CSS nesting spec:

Code playground
.parent {
  background: aquamarine;
  padding: 1rem;

  .child {
    background: honeydew;
  }

  & p {
    background: darkgoldenrod;
  }
}
Fixed it!

Why didn't it work?

There are restrictions on nested rule selectors.

The core issue is that when a parser encounters an identifier in a nested rule, it's not immediately clear whether that identifier is meant to be a property (like color, font-size, etc.) or a type selector (like div, span, etc.).

.someClass {
  color: {
    /* Is "color" a property or a selector here? */
  }
}

In this example, color could either be a CSS property that we're trying to set for .someClass, or it could be a type selector for a hypothetical <color> element nested inside .someClass.

This ambiguity makes it difficult for the parser to immediately determine the role of "color" in this context without additional information, which might require holding onto and analyzing an unbounded amount of text—which is undesirable because of efficiency.

To avoid this problem, CSS parsers in browsers enforce certain restrictions to make sure they can quickly and efficiently determine whether they are looking at a property or a selector.

Coming from React, Next.js and CSS Modules

What triggered me to delve into this gotcha is the following error:

Syntax error: Selector "img" is not pure (pure selectors must contain at least one local class or id)
Error output from Next.js
Error output from Next.js

It was really confusing how I could nest some selectors and not others and wanted clarity!

A word of warning

If we move the incorrectly specified p selector to the top of the ruleset, you'll see what happens.

Code playground
.parent {
  background: aquamarine;
  padding: 1rem;

  p {
    background: darkgoldenrod;
  }

  .child {
    background: honeydew;
  }
}
Entire nested rule is negated due to lack of & symbol before p

Any styles below it are negated; this is a feature of CSS, with React/Next.js you get a handy error letting you know that this has happened, otherwise you're left guessing.

Conclusion

If you incorrectly specify a nested rule, everything that comes after it will be negated.

When nesting type selectors (elements like p, img etc.) you need to use a symbol, in this case it's &, other symbols:

& @ : . > ~ + # [

Written by Morgan Feeney

I’ve been designing and developing for almost 2 decades.
Read about me, and my work.

For juicy tips and exclusive content, subscribe to my FREE newsletter.