Complex CSS code bases with BEM. Best practices, thoughts and recommendations.
5/7/2020 by Stefan BauerIn my last post, I wrote about Vue, TypeScript, application design and best practices. I briefly mentioned how great BEM is for maintaining CSS code bases and thought that was pretty self-explanatory. Obviously, I was wrong.
I never expected this to happen, but I got some emails and private messages by developers who either did not understand the advantages of BEM or struggled with CSS maintenance in general. At this point, I would like to thank you guys for your messages. They got me thinking and encouraged me to write about this topic. Also, it is really encouraging to get so much feedback. It makes me happy that my posts actually help people.
My CSS journey
You may skip this section, I just happen to like introductions that are a little more personal.
Although my first professional experience with software development was in the backend, my career somewhat started in the frontend. When I was around 13, my father tought me the basics of HTML. Frontend development was awful back then. Browser testing was painful and there were not too many useful articles out there. I created huge tables and sliced even bigger images. It was a special kind of hell, but I loved it. Over time I created a solid CSS library that had everything I needed. Simple buttons, a grid that used floats and worked well in all major browsers as well as some basic margin, padding and font setting normalizations.
Of course, there were other people who did a much better job at this. I swallowed my pride and started using CSS resets and frameworks such as Bootstrap. Reading the source code of these projects helped me to understand what good CSS actually was.
However, my frontends improved even more when I discoverd CSS methodologies. I started using SMACSS and thought this was the best thing ever. I used it for some professional projects and was very happy with the results. But my stylesheets still felt like append-only piles of styles. This changed when I adopted BEM.
Advantages of BEM
BEM stands for "Block Element Modifier". People generally dislike BEM selectors when they first see them. They are very
long and contain lots of _
and -
characters. Just have a look at these real-world examples:
.ara-template-browser {
}
.ara-template-browser__slide {
}
.ara-template-browser__slide--selected {
}
Stop for a moment and think about these examples, though. They make perfect sense:
block modifier
v v
.ara-template-browser__slide--selected {}
^ ^
namespace element
You can actually tell what a class does where, just by reading its name. That is incredible. How often have you
wondered which .highlight
styles apply to the element you are debugging?
The advantages of adopting BEM are:
- It helps you structure your components.
- It helps you to think in atomic components. You feel you need sub elements like
.my-block__list__item
? You do not. You need a.my-block
and a.my-list
with a.my-list__item
. - It helps you to find and clearly identify CSS classes in your codebase.
- It allows you to actually delete classes with confidence. You no longer need to be scared to remove these
.section
styles. - It lets you scale effortlessly.
If you would like see some more examples, I recommend looking at this BEM cheat sheet.
Best practices
Use namespaces
Namespaces are optional in BEM. They make class names even longer and many people dislike them for this exact reason. However, namespacs add another layer of clarity and portability and should never be omitted.
Do not be afraid of long class names
Being specific is very important in software development. You are writing code for other humans, not for computers. Have a look at the following examples. They are long and maybe even ugly, but there is a lot of useful information there.
.xyz-premium-user-benefit-header {
}
.xyz-premium-user-benefit-header__background-image {
}
.xyz-premium-user-benefit-header__logo {
}
Avoid selector nesting
PostCSS, SASS, LESS... they all offer nested CSS. And it looks so beautiful.
.demo-block {
width: 50%;
border: 1px solid transparent;
&:hover {
border-color: blue;
}
&--full {
width: 100%;
}
&__section {
margin: 1em 0;
padding: 1em;
border-top: 1px solid silver;
}
&__section--stacked {
margin: 0 0 1em;
}
}
This looks perfectly readable and makes a lot of sense. BEM seems to be made for this, right? Trust me on this - it is not.
I spent over 6 years believing that nested CSS selectors were the future and that I just had to figure out how to use them properly. However, nesting selectors simply has too many drawbacks.
- It quickly gets harder to read. There are many styles for each selector and not just one or two like in the example above. Your IDE will help you, but that is just not enough.
- You will be tempted to use nested selectors in your nested selectors. Do not do it. You will absolutely regret this when you have to refactor, debug or even delete this code six months later.
- It is bad for search and replace. Try searching for
&__section
instead of.demo-block__section
and see how much more results this search yields. We want to be able to delete styles with confidence. And no, this is not a naming issue. You will have generic names such assection
. - You should never format your code manually. You and your team should configure your IDE's code formatter and use it for each and every file. Some selector nestings do not work properly with auto formatting. At my company we use PhpStorm and especially PostCSS code was affected by this.
- There is no real value to selector nesting. One could argue that it makes renaming things easier, but I honestly think that search and replace makes renaming classes easy anyway. One could also argue that it looks much nicer. However, as software developers we should always strive to be pragmatic.
That being said, have a look at the more traditional version of the code snippet above. It is much easier to work with and not ugly at all.
.demo-block {
width: 50%;
border: 1px solid transparent;
}
.demo-block:hover {
border-color: blue;
}
.demo-block--full {
width: 100%;
}
.demo-block__section {
margin: 1em 0;
padding: 1em;
border-top: 1px solid silver;
}
.demo-block__section--stacked {
margin: 0 0 1em;
}
BEM and specificity
If you are not familiar with specificity, you should
read this article. It is an important concept. In
general, my advice is to keep specificity as low as possible and never use !important
.
Keeping your specificity low should be fairly easy, if you remember to place your base styles before your modifiers:
.demo-block {
width: 50%;
}
.demo-block--span {
width: 100%;
}
This is harder to override and is a little more annoying to read:
.demo-block {
width: 50%;
}
.demo-block.demo-block--span {
width: 100%;
}
Split your CSS files for complex modules
I recently had to write a podium element that supports different types of callouts. The layout itself was relatively
simple, but the callout styles quickly bloated my podium.css
. My webpack setup allowed me to split my code into
readable bits that are really easy to edit and refactor.
.
└── templates
└── podium
├── styles
│ ├── layout.css
│ ├── callouts.css
│ ├── items.css
│ └── variables.css
├── styles.css
└── template.html.twig
My styles.css
was just a collection of imports:
@import "styles/layout.css";
@import "styles/callouts.css";
@import "styles/items.css";
@import "styles/variables.css";
This is a Symfony, TWIG and PostCSS example, but it can easily be applied to your React SPA or your Laravel Blade components that use Stylus.
Each file has a semantic name which makes it easy to edit or extend it. You do not have to scroll through a "master stylesheet" to find all the styles you need for a specific module.
Build mobile first and place your media queries after the base styles
This is a bit more controversial. I know that many developers prefer something like this:
.demo-block {
width: 100%;
@media screen and (min-width: 768px) {
width: 50%;
}
}
.demo-card {
padding: 20px 80px;
}
Or something like this:
.demo-block {
width: 100%;
}
@media screen and (min-width: 768px) {
.demo-block {
width: 50%;
}
}
.demo-card {
padding: 20px 80px;
}
Both of these versions have some serious drawbacks. Version 1 uses nesting, which I dislike in general ( see "Avoid selector nesting"). However, it also shares all of version 2's mistakes.
- If you write styles like this, you will need many duplicate media queries. This will bloat your CSS (although minifying and purging your CSS will help you with that).
- You will have to refactor many media queries, if you need to update your breakpoints. (Of course, CSS preprocessors have other solutions for this problem. I still think it is a valid point.)
- Your media queries will be all over the place. It will be hard to figure out how all those styles work together.
- You usually want your media query specific styles to be grouped together.
Therefore, my recommended version is this:
.demo-block {
width: 100%;
}
.demo-card {
padding: 20px 80px;
}
@media screen and (min-width: 768px) {
.demo-block {
width: 50%;
}
}
Take a moment and think about it. Many bug tickets are phrased like this:
Bug: Large offset for sub headers on mobile devices.
Or this:
Bug: Text flows out of card headers on desktop devices.
Fixing those bugs will be much easier now, because you know exactly where to find your styles.
Use BEM even when you use CSS-in-JS
Many frameworks offer CSS-in-JS or scoped CSS. For example, consider the following Vue component:
<template>
<div>
This text is blue.
</div>
</template>
<style scoped>
div {
color: blue;
}
</style>
The blue text color will not leak into other components of your application, because some JavaScript magic will attach
an attribute to it (e.g. data-v-7f15de78
) that will also be addeed to your CSS selector (e.g. div[data-v-7f15de78]
).
That is cool and works really well.
I still recommend using BEM as well. It may be more wordy, but it has some advantages to it.
- It makes refactoring large components easier.
- It lets you port a module's styles to new projects with relative ease. At least I had to port many, many components to other projects.
- Again, it helps you separating framework and utility classes from your component's.
- Also again, it helps you think about your the design of your components in a more granular way. That is especially helpful when designing SPAs.
Always use BEM and start refactoring
Projects may start small, but some of them keep on growing. People are used to refactoing their backend or JavaScript code, but they usually prefer rewriting CSS. This is because CSS code health is often pretty bad. We should start changing this right now.
Write comments
Speaking of code health, I would like to mention that CSS code bases in general are in dire need of comments. Sarah Drasner wrote an article about this over at CSS Tricks, and I would highly recommend reading it.
Mixing framework code with BEM
BEM can easily be mixed with frameworks such as Bootstrap or Tailwind. Utility classes like .btn
or .container
cannot clash with your styles, because your BEM selectors work just as safely as scoped CSS generated with JavaScript.
Wrap up
There is much more to it, but these are the most important points that come to my mind. I hope that this encourages some of you to try out BEM and helps others to use it more effectively.
Cheers.