Remember how Marty McFly traveled to the past and changed that one small thing, then when he came back to the future (ohhhhhhhh) his dad was all buff and cool, instead of the sniveling weakling we saw at the start?
In the same way, CSS Modules has introduced one small change at the heart of css and now there is a new world of improvements to discover.
This post is a continuation from last week’s introduction to CSS Modules where we will take a closer look at some useful things we can do with it.
Sometimes we need something more than plain CSS. Let’s start with this familiar example:
.reallyGoodBox {
...
}
.reallyGoodBox.active {
...
}
.reallyGoodBox .inner {
...
}
.reallyGoodBox .inner .buttons {
...
}
Now there’s a lot of repetition here and we know that repetition in software is not great. On the other hand there are many who argue that duplication in code is bad.
But how do we deal with the repetition? Modern day tools like SASS and LESS give us an easy way to clean it up:
.reallyGoodBox {
&.active {
... }
.inner {
...
.buttons { ... } } }
It certainly looks cleaner, but both examples share a common underlying problem. As selector paths become longer they also become harder to maintain. Every time you want to override this default in another place you’ll need to find a way to 1-up the specificity of the selector. There’s a very good post about understanding selector specificity if you’d like a better idea of how this can affect the quality of your code.
In many cases such as this, nesting simply makes it easier for us to do an unhelpful thing, and masks the underlying problem.
If you’ve grown accustomed to nesting all of your rules it might seem outrageously primitive to use something like CSS Modules which, by default, doesn’t allow nesting.
But let’s take a look at how we would rewrite the earlier example as a CSS Module:
.reallyGoodBox {
...
}
.active {
...
}
.inner {
...
}
.buttons {
...
}
No nesting, no repetition. No selector specificity arms-race leading to the inevitable !important
.
This is an opportune moment to ask why we began nesting in the first place: it has everything to do with context. We can’t talk about .inner
on its own, we need to talk about .inner
in the context of the .reallyGoodBox
. Chained selectors like .reallyGoodBox .inner { ... }
gave us a way to describe context, but with a hidden cost. In CSS Modules the context is managed at the level of the module so that you don’t have to handle it manually within each rule. Because CSS Modules provides local scope for class selectors we can target them precisely to a certain node.
In this way CSS Modules hasn’t merely given us a new way to produce css, but an opportunity to discard an old pattern for a better one.
We really do love them. Patterns help us to solve problems in uniform ways without having to rediscover the principles each time.
But not all patterns are equal, and some do more harm than good. Consider the following example:
Problem: too sleepy, can't concentrate Principle: caffeine is a readily available stimulant Solution: moar coffee!
The reason we can safely call this an antipattern is because although it will appear to solve the problem at first, it does so with diminishing returns whilst also creating an unhealthy dependency. Follow this pattern and before long you’ll be sleepy because you don’t sleep because of all the coffee. And the weasels. Let’s not forget the weasels.
This illustrates a certain class of antipattern we see all the time in software, where the problems caused by applying the solution seem fixable by applying more of the solution. It results in an ever-increasing dependency to the point where the solution has become the problem.
I can’t think of a code example right now but if you’re interested you can use the GenericExampleFactoryManagerFactory
class to generate one.
So next time you reach for “Rule Nesting” as a way to improve your CSS, consider whether you truly want an easier way to create more selector chaining. Perhaps what you actually need is less selector chaining!
It’s ok. Me too.
Nesting rules is not bad in itself, and can be great when we use it responsibly. Because CSS Modules is implemented as a set of postcss plugins it’s super easy to add new plugins to the transformation. Here is an example of postcss-nested and some other plugins: https://github.com/x-team/starting-css-modules
What’s your favourite CSS (or SASS / LESS) feature? Tweet it at me: @joshwnj