I’ve been doing this CSS thing for over 15 years and I’ve seen a lot of change in that time. A lot of the time, I see a new CSS feature and immediately get excited about how helpful it’s going to be for me and the team in the long term. A good example of that is text-box-trim.
There are features that fill me with dread though and right at the top of that list is native CSS nesting. I know that opinion will be rather unpopular, but the whole deal of the site is front-end education for the real world, so I’m here to provide that for you today.
It stems from pre-processorspermalink
I was a big Sass fan — especially when my primary role was writing lots of complex, difficult CSS. One of the reasons I really loved Sass was the ability to nest my CSS because it paired beautifully with BEM like so:
- Code language
- scss
.my-block { padding: 2rem; &__element { background: red; &--modifier { background: blue; } } }
Sass converts that into the following CSS:
- Code language
- scss
.my-block { padding: 2rem; } .my-block__element { background: red; } .my-block__element--modifier { background: blue; }
Pretty neat, sure, but that’s only a simple, clean-cut example.
As codebases grow and blocks of CSS get unwieldy — making them more complicated — the nesting selector — &
— becomes harder and harder to keep track of. Often, it will not be what you think it is. To stem this, I started using a Sass variable — $self
— to help maintain control and even wrote about it in 2018 for CSS-Tricks.
- Code language
- scss
.component { $self: &; // The $self selector creates a reference for .component display: block; max-width: 30rem; min-height: 30rem; &--reversed { background: white; border-color: lightgray; // Here, we use $self to get the correct reference #{ $self }__child-element { background: rebeccapurple; } } }
This compiles to the following CSS.
- Code language
- css
.component { display: block; max-width: 30rem; min-height: 30rem; } .component--reversed { background: white; border-color: lightgray; } .component--reversed .component__child-element { background: rebeccapurple; }
Without that $self
variable, we end up with this rather awkward CSS:
- Code language
- css
.component { display: block; max-width: 30rem; min-height: 30rem; } .component--reversed { background: white; border-color: lightgray; } .component--reversed__child-element { background: rebeccapurple; }
The nesting selector reference isn’t predictable to most people, so seeing .component--reversed__child-element
in dev tools caused rather a lot of confusion at an uncomfortable rate. In fact, I’d say in general that nesting is a source of confusion.
I’m allergic to confusionpermalink
I have a pretty simple rule of thumb when it comes to codebases — especially CSS codebases: could a junior developer understand this code?
It’s one of the primary reasons I eschew Atomic CSS because while working as a freelancer for an agency, I watched their junior developer panic at the prospect of modifying a utility class-ridden component because they didn’t dare touch it, out of fear. That’s not acceptable.
I see nesting as a risk to my principle of ensuring a junior developer could understand code. Sure, no code is self documenting and we can and should add lots of comments — just like I teach in Complete CSS, but your code should also be scannable.
Native CSS also has a nesting selector — &
— which you would think works in near-enough the same was as Sass, but that’s not strictly true. As Kilian writes:
The
&
in nested CSS isn’t just replaced by the ancestor, which is what you might think, but its ancestor is also wrapped in:is()
- Code language
- css
body { & div { ... } } /* Doesn't become this: */ body div { ... } /* It becomes this: */ :is(body) div { ... }
You might be thinking “that’s cool, I can live with that”, but allow Kilian to remind you about specificity gotchas) that arise with :is()
. Again, this fails my principle of a junior developer being able to understand the code.
I think Frank sums up my feelings quite well about nesting:
Nesting was a solution to a developer problem, not an end-user problem. Nesting had no business being a native feature of the browser.
As a feature of CSS pre-processors (or post-processors when you consider PostCSS) I think nesting is easier to swallow because it only impacts the developer. The end-user gets the output of those tools. I think that is ok.
But, when a developer experience feature is baked into the browser, I start to get uncomfortable. I know I can be a bit of a curmudgeon about this stuff, but that isn’t my default setting. That comes from lots of experience of lots of different codebases in my years as a CSS consultant. I’m a stickler for learning from mistakes.
If you’re going to use CSS nesting, at least keep it simplepermalink
I know I’m not going to convert nesting fans today. What I hope to send you away with is at least a more cautious approach. Consider keeping your nesting shallow, for example.
- Code language
- css
a { color: red; &:hover { color: rebeccapurple; } }
This is a nice, single purpose, shallow nested :hover
pseudo-class that’s fairly scannable. I’m still not convinced though because could a junior developer understand that more than this?
- Code language
- css
a { color: red; } a:hover { color: rebeccapurple; }
It’s probably a good idea to use the nesting selector — &
— cautiously too because of the specificity issues Kilian explains so well in their article. Also, you’re probably going to experience frustration with what should be simple things like Mike did.
Caution is the key word not just for nesting, but all new CSS features. I close out Complete CSS with the following:
Sure, new features like
@layer
and@scope
, for example, are very handy, but just remember, we’ve built a rather simple website together, so we certainly don’t need to use@layer
or@scope
in this context. I’d keep those for complex projects that interact with nasty third party stuff, for example.The point I want to get across to you is, don’t feel behind, because thanks to CSS being a web standard, the CSS you wrote today or even ten years ago, will just work. Knowing how to make an impact with the CSS skills you have now, is so much more beneficial than stuffing the new stuff into your codebase, just because you can. Good CSS — as I see it — is pragmatic, deliberate and effective CSS.
I want to highlight again, good CSS — as I see it — is pragmatic, deliberate and effective CSS, for effect here because if you take one thing from this article, I hope it’s that. Also, I hope you take my principle of making sure your code can be understood by a junior developer because guess what: future you will be thankful for the simpler, more efficient code, that principle — more often than not — results in.