Skip to main content
Stefan Bauer, M.A.

Complex CSS code bases with BEM. Best practices, thoughts and recommendations.

In 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:

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.

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.

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.

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.