Chainable BEM modifiers

Envato Market has been using a css naming convention loosely based on BEM block__element--modifier syntax for the past two years and it has been instrumental in helping us update our front-end codebase and freeing us from legacy constraints.

About a year in, a problem was still bugging us. We had two different ways of using modifiers: single class and multiple classes.

Whilst both techniques are valid, they lend themselves for use in different scenarios. Plus, having two conventions using identical syntax in the same codebase is a recipe for confusion.


Single Class (aka @extend)

A single class modifier is delimited with a double hyphen -- to indicate that it is one of a set of possible variations of a module.

HTML
1
2
<!-- Large green button -->
<button class="btn--primary">

This method pushes all the logic into the CSS and makes use of Sass’ @extend to make the modifications. It’s ideal for modules that only need to make one modification at a time, plus it’s easy to understand at a glance.

Sass
1
2
3
4
5
6
7
8
9
.btn
  font-size: 20px
  background-color: grey

.btn--primary
  @extend .btn
  background-color: green
  font-size: 30px
  padding: 10px
CSS (generated)
1
2
3
4
5
6
7
8
9
.btn, .btn--primary{
  font-size: 20px;
  background-color: grey;
}
.btn--primary{
  background-color: green;
  font-size: 30px;
  padding: 10px;
}


Multiple classes (aka chaining)

When chaining modifiers together we start with a base class and then add modifiers to set or override properties. These are also delimited with a double hyphen -- to indicate that they make modifications.

This approach keeps the logic in the HTML and offers us the flexibility to configure any given module on the fly. It is best suited for modules with multiple modifiers that are designed to be mixed and matched. This is especially useful for elements that make up a core UI Library, such as buttons, icons, and typography. The downside is that the HTML becomes verbose and repetitive and you may clobber your styles if you are not careful with source ordering.

HTML
1
2
<!-- Large green button -->
<button class="btn btn--color-green btn--size-large">
Sass
1
2
3
4
5
6
7
8
9
10
.btn
  font-size: 20px
  background-color: grey

.btn--color-green
  background-color: green

.btn--size-large
  font-size: 30px
  padding: 10px

Ben Smithett, a fellow Envatonaut, has written an article entitled BEM modifiers: multiple classes vs @extend where he examines the two approaches in more detail.


Finding a better solution

While looking for a solution to our problem, I stumbled across Dan Telos’ article Sassier (BE)Modifiers in which he proposes a slight variation to the BEM syntax and introduces the concept of BEVM: block__element--variation -modifier.

This was an ”aha!” moment for us. All along, we had been treating both ‘variation’ and ‘modifier’ as the same without realising. With this newfound knowledge, we set about updating our modifiers to fit the BEVM model but with a few modifications.

Our ‘single class’ approach would be known as a ‘variation’ and not require any change to syntax or implementation, whereas ‘multiple classes’ would become known as ‘chainable modifiers’ and bring a new set of rules and syntax.


Chainable Modifiers

Chainable modifiers are denoted by a leading hyphen -, a namespace and a descriptor for the modification.

HTML
1
2
<!-- Large green button -->
<button class="btn -color-green -size-large">

As the name would indicate, chainable modifiers provide us with the ability to configure a module in the HTML with a short, concise syntax.

The golden rule is that chainable modifiers should never modify the same CSS property twice for a given module. This is to ensure that styles don’t get clobbered and that the order in which they are applied is irrelevant.

Below are some examples of how chainable modifiers are used in Envato Market’s UI Library.

HTML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- Icon -->
<i class="e-icon -icon-envato -color-green -size-xl -margin-right"></i>

<!-- Typography -->
<h2 class="t-heading -size-m -color-light">Heading</h2>
<p class="t-body -size-s">Paragraph</p>

<!-- Inputs -->
<input class="f-input -type-string -width-full">

<!-- Notifications -->
<div class="alert-box -type-success">
  <div class="alert-box__icon">
    <i class="e-icon -icon-ok"></i>
  </div>
  <div class="alert-box__message">
    <p class="t-body -size-m h-remove-margin">Success!!</p>
  </div>
</div>

But won’t that make things hard to find?

In the above example we have 3 different modules that use the -size namespace.

We have been using chainable modifiers for almost a year now on Envato Market and have yet to encounter any issues with this, but in the rare instance that you are unable to find what you’re after, the following regular expression is your friend.

Regular Expression
1
2
btn.+-color-green
/* module.+modifier */

Most text editors should support performing a regular expression search, below is how to achieve this using Sublime Text 3:

chainable modifier Regex


State and Utility classes

‘State’ and ‘utility’ classes are also a form of modifier and due to their high priority nature should always be applied after other modifiers so that their styles take precedence (with state classes at the very end).

The class order that we use in Envato Market is: js-hook block__element--variation -modifier h-helper is-state.

State

State classes are prefixed with is- and used to indicate that a module has been modified in some way by application logic, be it server-side code or JavaScript.

HTML
1
<input class="f-input -type-string is-invalid">

While we have a handful of global state classes such is is-hidden, we prefer to scope them directly to a module.

Sass
1
2
3
4
.f-input
  &.is-invalid
    color: red
    border: 1px solid red

Utility (aka Helper)

Utility classes are not your traditional modifier, instead they are single-use helper classes designed to perform a simple repeatable task. Prefixed with h- (or u- in other toolkits such as SUIT), they can be used stand-alone or chained to a module.

HTML
1
<h2 class="t-heading -size-xl h-text-align-center">

Special mention (JS Hooks)

JS Hooks are prefixed with js- and do not have any styles. Their whole purpose is to be used in JavaScript to target elements in the DOM.


Conclusion

Discovering that there is a clear distinction between a ‘modifier’ and a ‘variation’ has led us to re-evaluate and challenge our practices.

Migrating from BEM to BEVM was a relatively straight forward process that happened over a long period of time, and is still ongoing. Whilst we have mostly limited ‘chainable modifiers’ to the core UI library, there is nothing stopping us from using them more widely across the application.

Adopting the BEVM convention and (more importantly) introducing chainable modifiers has been instrumental in allowing us to build a super flexible UI Library. This has enabled us to configure our modules in the HTML with a short, concise syntax, reducing the amount of CSS that we need to write and increasing our development speed.