Break Out of CSS Nesting with Sass

Hero image for Break Out of CSS Nesting with Sass. Image by Bill Jelen.
Hero image for 'Break Out of CSS Nesting with Sass.' Image by Bill Jelen.

Using Sass to write CSS following methodologies like BEM is incredibly easy by utilising the parent selector. However, what this can also lead to especially if combined with a (modest) level of inheritance is a complex structure where you find yourself needing to 'break out' of the CSS nesting at that point in the stylesheet and instead attach a ruleset to the document root.

To answer this problem, enter @atroot!

In simplest terms, you can think of @atroot as a bakedin mixin where anything you place inside of it no matter where you are within the stylesheet or nesting at the time will be emitted up to the document root level.

For example:

.classname {  color: black;  .child {    color: lime;    @at-root {      color: pink;    }  }}// Generated CSS:.classname {  color: black;}.classname .child {  color: lime;}.child {  color: pink;}

It can be written either as a mixin (@atroot { ... / <selector> }) where you wrap multiple styles or rules which are then trussed to the document root, you can also write it inline with a selector (@atroot <selector> { ... }) to emit the entire proceeding block.

The code below will achieve the same output CSS as above:

.classname {  color: black;  .child {    color: lime;  }  @at-root .child {    color: pink;  }}

Obviously, the whole point of BEM is to try and avoid inheritance because browsers match selectors from right to left and therefore the longer a selector is, the more computationally expensive (and thus: slow) it is. However, there are use cases where a single level of inheritance broken out of the nesting flow and attached to the document root is extremely useful.

For example, I like to use @atroot within mixins where the appearance of an element needs to change based on a classname attached to (or changed on) the <body>. In this situation, you need to elevate the styling ruleset up to the document root, and nest directly below body.classname in order to add a level of specificity and avoid inheritance screwing things up:

// Mixin@mixin whenState($el) {  @at-root body.state {    #{$el} {      @content;    }  }}// Use in Sass.element {  color: lime;  &__child {    color: purple;    @include whenState(&) {      color: pink;    }  }}// Generated CSS:.element {  color: lime;}.element__child {  color: purple;}body.state .element__child {  color: pink;}

In this way, you can retain your BEM structure and organisation whilst also breaking out of the generated CSS at the opportune point where a single element needs different styling depending on a classname further up the DOM. It's exactly how I implemented dark mode on my website.

See here for the Sass documentation on @atroot and a few more use cases and examples.


Categories:

  1. CSS
  2. Development
  3. Front‑End Development
  4. Sass