Skip to content

Instantly share code, notes, and snippets.

@mattidupre
Last active November 21, 2018 17:34
Show Gist options
  • Select an option

  • Save mattidupre/9b80fd8650e3e0a925195aac1f6f324b to your computer and use it in GitHub Desktop.

Select an option

Save mattidupre/9b80fd8650e3e0a925195aac1f6f324b to your computer and use it in GitHub Desktop.
BEM Overview

Nutrien and BEM

Introduction

BEM stands for Block Element Modifier. Or, block__element--modifier. It's a syntax for combining similar blocks of tags together and making it easier to target them in your CSS. Most people hate it at first, but over time you will come to appreciate it. As with any methology, there are different ways of doing things. This document should therefore live on as working document to capture any decisions made on the topic moving forward.

But Why?

BEM is ugly. Very ugly. But the benefits far outweigh the drawbacks. BEM keeps your CSS organized and discourages you from writing declarations that overlap one another. Even more importantly, it lowers specificity for situations when that can be avoided. Lower specificity means less time spent in the document inspector trying to figure out which decorations are ruining your CSS groove.

Specificity

It's important to know how specificity works in CSS. It feels like dark magic, but the formula is pretty simple. Imagine a four digit counter, like one of those train station clocks. 0 0 0 0. Each of those numbers represents a particular type of selector: inline style, id, [pseudo] class, attribute, <element>. In the case of the last three digits, a single selector will incr ease that digit by 1. Inline styles, found outside your CSS stylesheet and instead be attached to the element, (e.g., <div style="background-color: red;" />), make up the first digit. You can calculate specificity by looking at the number that is formed. For instance, div > .don.cheadle > #dontusethese will carry a specificity of 0 1 2 1 or 121. However, an inline style carries a specificity of 1 0 0 0, or 1000, and would override the previous CSS declaration. Finally, !important styles will override any number. If two declarations are !important, then the declaration with the highest specificity wins. Confusing? Play with it more: CSS Calculator.

So what?

Low specificity is a good thing. How many hours have you lost in your document inspector, trying to figure out why your style won't apply? BEM allows most declarations to carry a weight of 10. So rather than writing .heading .logo (with a specificity of 20) we can write .heading__logo.

Moving forward, think of any style that carries a weight above 10 as code smell. Even if it doesn't affect your current workflow, it will very likely affect someone else who has to work off of the same elements. Say you form a selector with a specificity of 30. That means the next person is going to either have to select with a specificity of 40. And so forth. Eventually this snowballs to the point where debugging class names becomes prohibitively time-consuming.

Implementation

Blocks and Elements

Creating a BEM class is simple. Blocks refer to the top-most element. In React this will almost always refer to the component name, but our component library will likely have many other semantic tags that function similarly. It is important to note that multiple blocks may exist in the same context. That is to say, a content block may also contain a card block. Elements, meanwhile, refer to all children of that element. Say we have a block named named header. We might have an element belonging to that block named branding. However, we may also have an element named logo that sits within branding. That's fine. Our BEM class names will thus be header, header__branding, and header__logo. Also note that it is perfectly valid to have some overlap. For instance, header__logo may refer to the layout rules affecting the logo while a logo block may also exist that handles things like coloration.

Modifiers

Modifiers may be attached to both blocks and elements and apply some sort of thematic change to the element. A modifier might be a simple addition such as card--shadow, or it might be a something more complicated like a margin size such as card--width-8px. One place we avoid using modifiers with classes is when that class is applied dynamically in JS (or outside the render function in React). Refer to the caveats section for more on this. Multiple modifiers may be applied to a tag. When doing so, you just repeat the whole enchilada for each respective modifier. For instance, <div class="card card--shadow card--width-8px. Yes, it's messy.

BEM in CSS

Remember our goal of keeping specificity low in our stylesheets? This is our time to shine. When writing BEM declarations, we (almost) always target them with a single selector. Referring back to our card component, we will end up with the following:

.card {}
  .card--shadow {}
  .card--width-8px {}

When working with elements, we do the same:

.card__heading {}
  .card__heading--bigger {}
  .card__heading--smaller {}

Notice how, by attaching modifiers to elements rather than blocks, we reduce the need for multiple selectors? The following should be considered a code smell and should be avoided:

.card {}
.card--bigger {}
  .card__heading {}
.card--bigger .car__heading {} /* Bad! */

There are times when this can't be avoided. There is more discussion on that below, under Caveats.

BEM in SASS

SASS allows us to nest our elements inside our blocks without increasing specificity. The & variable allows us to refer to the parent scope. So working form the above example, we can write:

.card {
  /* Card style */
  &__heading {
    /* Card style */
    &--modifier {
      /* Card style */
    }
  }
}

In edge cases where having multiple selectors is unavoidable for a particular set of rules, successive &s will need to be wrapped in a SASS template literal #{&}. Note the # versus the $ typically used in JavaScript.

.card {
  &--this#{&}--alsothis {}
}

The subjective part

The biggest challenge when working with BEM is figuring out which elements belong in which blocks. There is no fixed rule. Even in React, a block may point to a specific component but it may include multiple elements that themselves separate components. Furthermore, it is often a challenge naming elements in a strictly BEM fashion. Take, for instance, card__header and card__content-header. This is fine on occasion where things cannot be broken down further.

Regardless of your approach, remember that the ultimate goal here is to reduce specificity. If you're doing that, you're already doing great!

A nice primary article on BEM.

Caveats

Javascript state and BEM

Many developers shy away from modifying BEM classes from Javascript. Naturally most will be rendered within the render function of React components and that's fine. If you are setting up classes that toggle state, however, it can often be more expressive (and simpler) to name your classes accordingly. For instance, a button might have the class is-active. Thus, even at a quick glance, it is apparent that this class is 1) controller by JavaScript and 2) subject to change dynamically.

An alternative to JavaScript-specific classes is to namespace them, as discussed below.

Namespacing

Nutrien convention TBD. Namespaces should be followed by a - and the rest of the class name.

Some projects will also create a separate namespace for JavaScript hooks. Instead of selecting tags based on the same BEM classes used for styling, they will create separate classes. For instance, header__logo might be targeted by a separate js-header__logo class.

More on namespaces.

UAT test hooks

When implementing Selenium tests, it is a best-practice to avoid selecting tags based on the same selectors used by the stylesheets. If a style is changed, the test might inadvertently be broken. One solution is to namespace those classes as discussed for JavaScript above. A more effective option is to simple target based on a custom data-test attribute. This warrants discussion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment