codete what is bem and why its not what youre looking for main 960ac6f84e
Codete Blog

What Is BEM and Why It’s Not What You Are Looking For

Wojciech Kasprzyk a03bb76a67

02/03/2023 |

13 min read

Wojciech Kasprzyk

While learning any modern frontend JavaScript frameworks, you will probably come across the BEM CSS methodology (BEM stands for Block Element Modifier). 

It will likely be presented as a brilliant (if not the best) solution for common CSS problems, a way to write self-documented code comfortably and keep best practices. Especially when dealing with highly scalable code of large-scale applications or systems that have been maintained by big teams – or multiple teams – for many years. 

What you won't learn in a two-hour "getting started" course is that things are a little different in real life... But that's what this article will help you to understand.
 

Table of contents:

  1. BEM naming convention for CSS classes – promises vs. reality
  2. How to get self-documenting code in BEM
  3. Nesting elements with BEM
  4. BEM boilerplate
  5. Naming hell
  6. When can we use BEM, really?
  7. Is there no space for BEM? – conclusion
  8. Bibliography

 

BEM naming convention for CSS classes – promises vs. reality

BEM documentation claims that "BEM methodology solves the collision issue using naming conventions for CSS classes, providing unique names for all components and their parts," and then they add that "Using naming conventions allows us to

– Define unique names for BEM entities.

– Track hierarchical relationships within a block.

– Simplify the code.

– Get self-documenting code."

It's not true. The BEM naming convention for CSS classes makes the names more specific, but it does not guarantee that they are unique. At all. 

In large-scale applications, it's highly possible that classes like `active` will collide. However, classes like `form__button--active` are also likely to collide, and this probability scales with the size of the application. In fact, BEM does not prevent us from naming collisions but makes them rare enough to make us feel safe. 

To be more precise, I have to say that BEM does not do the magic on its own, but we, as developers, can use it to prevent name collisions. Ultimately, the uniqueness of class names is determined by our creativity and knowledge of the project we are working on, and BEM is only a tool that can help us to some extent.

Going further, I do agree that the naming convention allows us to define unique names for groups of "BEM entities." But they work only sometimes for specific classes of blocks, elements, and modifiers. True, this allows us to track the blocks’ hierarchy, but not always for deeply nested entities. And true, it will enable us to simplify the CSS code – but only until we use any kind of encapsulation or any CSS preprocessor. 

Finally – true, it allows us to get self-documenting code, but Angular/Vue/Svelte/React components do it equally well, if not better.
 

How to get self-documenting code in BEM

The BEM naming convention represents the HTML structure itself, at least in most cases, but it's nothing special – SCSS does it as well and works in all cases. Using nesting, we can use BEM with CSS preprocessors like SCSS (and, by the way, native CSS nesting is coming soon). It's comfortable to use both SCSS and BEM because you can nest styles, elements into blocks, and modifiers into elements, eliminating the need to repeat block and element names. 

What we cannot skip, however, is using the names in the template. Here’s what nesting styles look like in CSS and HTML.  

// file.scss
.profile-card {
   &__firstname {}
  
   &__lastname {}

   &__message-input {
       &--active {}
       &--validation-error {}
   }

   &__submit-button {
       &--disabled {}
   }
}

 

<!-- file.html -->
<div class="profile-card">
   <span class="profile-card__firstname"></span>
   <span class="profile-card__lastname"></span>
   <input class="profile-card__message-input profile-card__message-input--active profile-card__message-input--validation-error">
   <button class="profile-card__submit-button profile-card__submit-button--disabled"></button>
</div>

The above example is pretty clear in the SCSS file view, but it gets a little jumbled in the HTML view, reducing readability. The price brings the value of uniqueness and self-documentation, but we can still achieve better results for both by paying a smaller fee.

BEM is based on the concept of keeping specificity as low and as equal as possible. Long story short, every element has a class, and no nested styles increase specificity. Many articles repeat that the tool has to help us predict which style in code wins or which is more specific3,4, but I don’t see any reason for any styles to fight each other. Usually, we don’t want to override any style, as doing so reflects other potential issues with the application and its architecture. Even contrast and dark modes can be implemented using an additional wrapping class or CSS layers. 

Putting so much attention into keeping specificity low by creating unique class names harms the readability of the HTML template. It generates a lot of unnecessary boilerplate, especially while using encapsulated components

The same example could be written as follows:

// file.scss
.profile-card {
   .firstname {}
  
   .lastname {}

   .message-input {
       &.active {}
       &.validation-error {}
   }

   .submit-button {
       &.disabled {}
   }
}

 

<!-- file.html -->
<div class="profile-card">
   <span class="firstname"></span>
   <span class="lastname"></span>
   <input class="message-input active validation-error">
   <button class="submit-button disabled"></button>
</div>

The specificity of nested classes is greater, but this is fine as long as we keep other best practices. Even the influence on performance will be irrelevant since the type of CSS selector does not change a lot7,8,9.

In some cases, BEM boilerplate can be quite helpful – meaningful class names help us to understand the purpose of the HTML tags and structures we created. But again, we can achieve all this by paying less.  

Sometimes it's preferable to simply document the code with comments rather than generate its "self-documented" version that includes all of BEM’s boilerplate. For example:

<!--SELF DOCUMENTED-->
<div class="form-field form-field--firstname">
   <input id='firstname' type="text">
   <label for="firstname"></label>
   <div><!--some other code--></div>
</div>

<div class="form-field form-field--lastname">
   <input id='lastname' type="text">
   <label for="lastname"></label>
   <div><!--some other code--></div>
</div>

<div class="form-field form-field--age">
   <input id='age' type="text">
   <label for="age"></label>
   <div><!--some other code--></div>
</div>


<!--DOCUMENTED MANUALLY-->

<!--FIRSTNAME-->
<div class="form-field">
   <input id='firstname' type="text">
   <label for="firstname"></label>
   <div><!--some other code--></div>
</div>
<!--LASTNAME-->
<div class="form-field">
   <input id='lastname' type="text">
   <label for="lastname"></label>
   <div><!--some other code--></div>
</div>
<!--AGE-->
<div class="form-field">
   <input id='age' type="text">
   <label for="age"></label>
   <div><!--some other code--></div>
</div>

Because all three inputs look the same and only the `form-field` class styles anything, added modifiers will document the code poorly by slightly increasing the bundle size and adding unused classes. 

Of course, if they add anything to styles, it makes sense to use them, but usually – we aim for a consistent design.

Nesting elements with BEM

BEM allows us to nest elements but does not get grandchild elements.

<!-- ALLOWED -->
<ul class="menu">
   <li class="menu__item">
       <a class="menu__link" href="https://">...</a>
   </li>
</ul>

<!-- PROHIBITED -->
<ul class="menu">
   <li class="menu__item">
       <a class="menu__item__link" href="https://">...</a>
   </li>
</ul>


In such a case, the BEM naming convention does not allow us to understand the structure of a block without studying the HTML in detail (~BEM documentation) anymore.

We know a `menu-link` is inside the `menu,` but we cannot predict whether it's nested inside `menu-item.` Furthermore, we don't know how many elements there are; how many `menu-link`s, how many `menu-item`s, and how many `menu-link`s inside of `menu-item`s. So it's totally irresponsible to rely on the BEM naming convention as it's confusing, inaccurate, and incomplete.

Since it only reflects the reality in specific cases, it cannot be considered as a general advantage of the methodology but – at most – as an occasional side effect.

BEM boilerplate

Let's return for a moment to the BEM boilerplate, which, as you may recall, was already mentioned in the self-documenting code paragraph. The official documentation for BEM contains a few simple examples that can demonstrate BEM usability, but they must be more extendable.

The section on  BEM tree, one of the BEM key concepts, officially uses the following example:

<header class="header">
   <img class="logo">
   <form class="search-form">
       <input class="input">
       <button class="button"></button>
   </form>
   <!-- ... -->
</header>

As you can see, the logo, the input, and the button are all blocks – not elements, which makes no sense. The names don't seem to be unique and don't self-document the code. To keep the naming convention, we could have used `header__logo` or `logo logo--header` classes instead. As a result, the mentioned part of the example would (and should!) look like this:

<header class="header">
   <img class="header__logo">
   <form class="search-form">
       <input class="search-form__input">
       <button class="search-form__button"></button>
   </form>
   <!-- ... -->
</header>

Or, in the case of hosting multiple entities on the same DOM node and to avoid copy and paste – more like this:

<header class="header">
   <img class="logo logo--header">
   <form class="search-form">
       <input class="input input--search-form">
       <button class="button button--search-form"></button>
   </form>
   <!-- ... -->
</header>

Actually, the single `input` tag used on the official BEM doc page (except for radio inputs) could still have a single `input` class, keeping it unique. This speaks for the actual example’s usability for a professional project. But as we know, reality differs slightly, and even BEM methodology does not save its official documentation source code from needing to be clarified. 

Just look at the following code below, found in the BEM documentation.   

<form class="form search__form i-bem">
   <span class="input input_type_search i-bem input_js_inited">
       <span class="input__box">
           <input class="input__control i-bem input__control_js_inited">
       </span>
   </span>
   <button class="search__submit">
       <!--... -->
   </button>
</form>

It seems great, as styles tell us a lot about structure, and we can clearly predict that there is an input element inside a box – which is most likely required to position this input or set any other complex styling. There is even one extra span as Block keeps everything inside, probably adding more styles. 

However, this needs to be corrected. The `input__box` element does not style anything, and the `input` Block provides styles that can be easily moved to the input tag, allowing them to remove both spans.

As you can see, using BEM cannot simplify or self-document the code. But, using BEM with good architecture can reflect the already existing good architecture. Using it with cluttered architecture, on the other hand, will only add to the confusion.

Naming hell

Setting classes for all page elements is a costly and now obsolete practice. While using the most popular frameworks, libraries, and compilators (React/Angular/Vue/Svelte). We want to create as many components as possible while keeping them as small as possible and – in majority – as presentational (dumb) components. 

Giving them all classes only makes sense if we stick to the meaningful names suggested by the framework of our choice. Let's look at the form field created in React.

import classes from './Form.module.css';

function Form() {
   return (
       <FormField>
           <Input label="firstname"/>
       </FormField>
   )
}

function FormField({children}) {
   return (
       <div className={classes['form-field']}>
           {children}
       </div>
   )
}

function Input({label}) {
   return (
       <div className={classes["container"]}>
           <input />
           <label>{label}</label>
           <div className={classes["validation-error-msg"]}></div>
       </div>
   )
}

Because CSS modules are scoped, we can skip naming input and label elements – we need to nest both elements into the `container` class. Still, higher specificity is not a problem, especially in such a small component7,8,9. Nested styles provided by CSS preprocessors like SCSS also reflect HTML structure, allowing us to understand the structure of a block without studying the HTML in detail (~BEM documentation).

The Angular, Vue, and Svelte examples are even simpler and more readable because they support encapsulation styles. We don't have to nest native tags into any `container,` so we no longer need the `form-field` class. Moreover, FormField and Input elements are self-documented; adding a class would only redundantly worsen readability.

Our components should have meaningful names, and luckily – many HTML tags are self-explanatory, it is usually unnecessary to document them by assigning a class. For example, the `label` tag is usually used as a label, and the `li` tag stands for `list item` – `list-item` class, in this case, would be redundant until it changes any style.

Sticking to the naming convention causes another ridiculous consequence – BEM documentation adds a `page` class to the body tag since there has to be only one body tag in an HTML document. It does not prevent collisions, as any collision is impossible in this case. It does not keep specificity as low as possible (increases it instead), and finally, it does not make styles more readable –  because the developer would not expect the body tag to have a `page` class. 

As I mentioned above, naming all tags is redundant. Doing so solely because some methodology says we should is highly irresponsible, uncritical, and blind, exposing our lack of knowledge and thoughtlessness.

When can we use BEM, really? 

Actually, some form of BEM or its components could be helpful, especially when we cannot use styles encapsulation, any CSS preprocessor, or... in ReactNative.

ReactNative StyleSheets are quite convenient, as they are scoped and use dictionary structure. Unfortunately, they cannot be nested, preventing us from understanding the template structure. BEM makes it more readable, hostable, and even more convenient, noticeably improving the code quality.

function App() {
   return (
     <View style={styles.screen}>
         <View style={styles.title}>
             <Text style={styles.title__text}></Text>
         </View>
        
         <View style={styles.list}>
             <ScrollView style={styles.list__container}>
                 <View style={[styles.list__item, styles.list__item_light]}></View>
                 <View style={[styles.list__item, styles.list__item_dark]}></View>
             </ScrollView>
         </View>
     </View> 
   );
}

const styles = StyleSheet.create({
   screen: {},
   title: {},
   title__text: {},
   list: {},
   list__container: {},
   list__item: {},
   list__item_light: {},
   list__item_dark: {},
});


It still needs to solve the problems of grandchildren and nested blocks, but it is useful in developing, improving readability and the ability to style hosting. And, because it is a tool that works for us (rather than the other way around), we can assume we are permitted to use grandchildren in code within the confines of our own preferences. Or logic. Or both.

Is there no space for BEM? – conclusion

For many years, BEM has been efficiently and significantly improving the experience of frontend developers. Even today, it’s still helpful at the beginning of the frontend journey to know good (enough) practices provided by the BEM methodology. Moreover, having some hands-on experience with the most well-known tools is even more beneficial, as they may one day become your leverage to jump into your next long-awaited project. 

As we gain experience, we should rethink our knowledge to become more conscious programmers. For example, you've just discovered that understanding BEM limits and boundaries is critical when deciding whether or not to use the tool in our project. Overall, we should use tools to accomplish something rather than just for the sake of using them. And certainly not because they have recently become popular – but to add value to our upcoming projects.


Bibliography

  1. BEM official documentation – https://en.bem.info/methodology/quick-start/
  2. MDN web docs – Specificity – https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity
  3. Dirk – ‘Why BEM?’ in a nutshell https://blog.decaf.de/2015/06/24/why-bem-in-a-nutshell/
  4. guntherWebDev – CSS BEM to keep up with modularity https://medium.com/@guntherWebDev/css-bem-to-keep-up-with-modularity-e7f2b9f8bfd9
  5. Patrick Brosset – The truth about CSS selector performance https://blogs.windows.com/msedgedev/2023/01/17/the-truth-about-css-selector-performance/
  6. Kevin Kreuzer – The difference between Angular’s Emulated Shadow DOM and native Shadow DOM – https://kevinkreuzer.medium.com/the-difference-between-angulars-emulated-shadow-dom-and-native-shadow-dom-9b2c81546b85
  7. Ben Frain – Appendix 1: CSS selector performance – https://ecss.benfrain.com/appendix1.html
  8. Steve Souders – Performance Impact of CSS Selectors https://www.stevesouders.com/blog/2009/03/10/performance-impact-of-css-selectors/
  9. Harry Roberts – Writing efficient CSS selectors – https://csswizardry.com/2011/09/writing-efficient-css-selectors/
Rated: 5.0 / 5 opinions
Wojciech Kasprzyk a03bb76a67

Wojciech Kasprzyk

Senior Frontend Developer at Codete with 6 years of experience. I am a big fan of flexibility and being open-minded in software development. Every challenge is an opportunity for me to grow by learning new things, understanding other people's perspectives, and reconsidering my beliefs.

Our mission is to accelerate your growth through technology

Contact us

Codete Global
Spółka z ograniczoną odpowiedzialnością

Na Zjeździe 11
30-527 Kraków

NIP (VAT-ID): PL6762460401
REGON: 122745429
KRS: 0000983688

Get in Touch
  • icon facebook
  • icon linkedin
  • icon instagram
  • icon youtube
Offices
  • Kraków

    Na Zjeździe 11
    30-527 Kraków
    Poland

  • Lublin

    Wojciechowska 7E
    20-704 Lublin
    Poland

  • Berlin

    Bouchéstraße 12
    12435 Berlin
    Germany