Break Out of CSS Nesting with Sass

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 with a few levels of nested selectors ‑ is a complex structure where you find yourself needing to 'break out' of the CSS nesting at that point in the stylesheet and emit a ruleset outside its parent selectors.
To answer this problem, enter @at-root!
In simplest terms, @at-root is a Sass at‑rule that emits its nested style rules without the surrounding selector nesting. It changes the generated stylesheet, not where an element sits in the DOM. By default it preserves enclosing at‑rules such as media queries.
For example:
.classname {
color: black;
.child {
color: lime;
@at-root {
.child {
color: pink;
}
}
}
}
// Generated CSS:
.classname {
color: black;
}
.classname .child {
color: lime;
}
.child {
color: pink;
}You can use a block, as above, with an explicit selector inside it. Alternatively, put the selector directly after @at-root to emit that rule outside the surrounding selector nesting.
The code below will achieve the same output CSS as above:
.classname {
color: black;
.child {
color: lime;
}
@at-root .child {
color: pink;
}
}
BEM helps keep selectors independent of the surrounding markup. Browsers generally match selectors from right to left, but selector length alone does not tell us whether a stylesheet is slow. Profile the page before treating this as a performance problem. Here, the useful part is controlling which ancestor selectors appear in the output.
For example, I like to use @at-root within mixins when an element needs different styling depending on a classname on the body. This mixin emits body.state followed by the current element selector, without carrying its other surrounding selectors into that rule:
// 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 @at-root and a few more use cases and examples.