A comprehensive guide to using BEM with React

Asís García
Trabe
Published in
7 min readFeb 18, 2019
Photo by 𝚂𝚒𝚘𝚛𝚊 𝙿𝚑𝚘𝚝𝚘𝚐𝚛𝚊𝚙𝚑𝚢 on Unsplash

In previous stories (“Using BEM conventions in CSS modules leveraging custom webpack loaders” and “A more JavaScript-friendly BEM naming convention”) we’ve discussed how using CSS Modules imposes some restrictions on which CSS class names are “valid”, and how those restrictions drive the “BEM flavor” we’ve chosen to use at Trabe

In this story I want to broaden the focus and give you a more detailed view on the way we use BEM naming conventions together with some tooling in our React projects.

CSS Modules

CSS Modules lets you write CSS files where all the class names have local scope. This means no more CSS naming conflicts! How does this work? Well, you write “normal” CSS files, but instead of writing the class name verbatim in your component code, you import the CSS file and use the imported class names (see the example below):

The CSS Modules plugin or loader (depending on whether you are using PostCSS or Webpack to process your CSS Modules) will generate a unique name for each class name in the CSS file. So, when you use styles.Button in your component’s code, the actual class name will be something like button.css_Button___231as3s. This way you get non-clashing class names while you are still able to write “old-school” CSS.

The way local class names are transformed into global, non-clashing class names, can be configured. For instance, in our projects we configure Webpack CSS loader to use a global index, so we get simpler class names, like Button----1. These are easier on the eyes when you inspect the code using developer tools 😄.

You will have to configure your test environment so it “understands” CSS Modules. For Jest we configure a moduleNameMapper to get predictable class names in our tests, using identity-obj-proxy:

BEM naming conventions

CSS Modules solve the problems derived from CSS having a global naming scope. When you write a component, you know that the class names you use will be “unique”, no matter what other components or even third party libraries do.

But you still have to tackle some problems which can appear at a local, single-component level:

  • Having truly composable components 😅
  • Naming consistency and semantics
  • Selector specificity

This is where BEM naming conventions come into play.

Having truly composable components

When you define a React component following BEM conventions, you must know that:

  • A Block “encapsulates a standalone entity that is meaningful on its own”. So, every React component you write must have a Block class name on its outermost element.
  • Elements are “parts of a Block and have no standalone meaning”. So Element class names should be given to inner… elements.
  • Modifiers are “flags on Blocks or Elements” which you can use to change the appearance of the modified item.

From the definitions above we derive two different sets of CSS properties, one for Blocks, the other one for Elements (Modifiers just use the same properties as the item being modified):

  • Block-related properties. A Block being a “standalone entity”, its properties should only affect appearance (background-*, color, font-*, border-*, padding-*, width, height, line-height, text-align, etc.) and/or the way it arranges its Elements (flex, grid, position).
  • Element-related properties. They should only affect the way the Element is positioned within the component and relative to other Elements (margin-*, top, left, right, bottom, z-index, align-self, justify-self, grid-column, grid-row, etc.).

How does this distinction affect composability? Well, each set of properties has a different scope. Block-related properties are “context-free”, in the sense that they can’t affect or be affected by where you place the Block. Element-related properties, on the other hand, are “context-sensitive”, because they do affect and are affected by its “surroundings”.

Limiting Block CSS properties to the “context-free” ones, and limiting Element-related properties to the ones having to do with positioning, guarantees that your components can be used as “standalone entities”, making them composable.

You may argue that this is not what the original naming convention says. There, no distinction is made on what properties apply to Blocks or Elements. But we have found that with this restrictions in place, it is much more easier to reason about the component structure (at both visual and code levels).

Naming consistency and semantics

Having a consistent and predictable naming convention is key to have a maintainable project. Being able to know the role each part of a component plays just by looking at its class name, has proven to be really useful for us while doing code reviews.

As I described in “A more JavaScript-friendly BEM naming convention”, we use the following BEM naming convention in our most recent projects:

  • Blocks are written using PascalCase, like in VideoPlayer.
  • Elements are written using camelCase, joined to the block name using a single underscore (_), like in VideoPlayer_buttonsContainer.
  • Modifiers are written using camelCase, but we use three underscores (___) to join the modifier to the block or element name, like in VideoPlayer___isLoading.

When we review code, we can immediately tell if a component lacks the relevant class names and, conversely, when we see each class name being applied to different parts of a component, we get a clue of that part’s role inside the component.

Also, as we’ve seen while talking about truly composable components, in theory (see the final notes to know more about the “in practice” side of things 😅), a component should only have a Block class, assigned to the outermost element of the component, and Element classes assigned to the inner elements. This leads to the following pattern:

  • There is only one Block class name (that of the component itself) in a React component’s source code file.
  • The rest of class names used in the source code have to be Element class names.

The use of class names diverging from the previous pattern is a code smell and should be reviewed.

Selector specificity

The problem of selector specificity is similar to the problem of global scope. You might have CSS Modules working and still face problems of “unwanted” styling:

In the example above, the Button text will be red when rendered inside the Footer component, despite the use of CSS Modules. While this behavior (due to the .Footer a selector having a highest specificity than the .Button one) is very useful to style a “website”, it can be frustrating when you are trying to style (supposedly) isolated “components”.

BEM imposes a restriction on the type of selectors you can use: only class name selectors. It also limits the use of CSS combinators to a single descendant selector, and only when you want to “alter elements based on a block-level modifier” (think something like .Button___disabled .Button_icon { display: none };). This means the code in the previous example is not valid from a BEM standpoint. A possible solution will involve creating a Link component and using it inside the Footer:

Now Button and Link styles don’t interfere with each other.

CSS formatting and linting

To prevent the most common errors before the code gets to the review process, we use stylelint with a bunch of plugins and configuration presets (adding prettier to the mix to ensure all the CSS code is uniformly formatted):

The following configuration file ties it all together. Pay special attention to the selector-bem-pattern configuration. This is the plugin we use to make sure we’re following the established BEM naming conventions:

With this configuration in place, you get lint errors whenever an invalid selector is used, forcing you to review your code.

React components and BEM

A typical React component using the conventions I’ve outlined in the previous sections will look like this:

There are a few things to note in the example:

  • We use classnames and computed property names to apply the Block and possible Modifier class names.
  • Most of the time we prefer defining Elements with its own div, like we did above withButton_icon, wrapping other components. But in some cases using a wrapper can cause problems (mostly when dealing with flexbox and grid layouts) and the Element class has to be given to the component itself (<Icon /> in the example). That’s why our React components always accept a className prop. We use this prop, together with the component Block and Modifiers, to compute the class names which will be actually applied to the component wrapper. This way we can give our Block components an additional Element class name when they are used by other components.

Escape hatches and some nuances

Being 100% compliant with the conventions and restrictions I’ve described in this story is not always the best solution in terms of productivity. So we’ve come to use some “escape hatches”.

Auxiliary components

We allow the definition of auxiliary components in the same files (both .jsx and .css) as the “main” one. We treat this auxiliary components as BEM Blocks too, and they can have their own Elements and Modifiers.

To prevent stylelint from failing, we have to use a special directive to declare a new component in the main .css file (remember that our configuration infers the name of the Block from the file name):

Fixed positioning

For fixed-positioned Blocks, we allow them to receive position-related properties:

Non-BEM selectors

Sometimes we have to style some element deep in the DOM hierarchy. In those cases we use “forbidden” CSS combinators, disabling the linter as needed:

Third party code and global CSS

When we need to style some third party components via CSS class names, we use the :global switch in CSS Modules to prevent the class name from being “modularized” (no need to disable the linter here, as global selectors are not processed):

Parting notes

Embracing BEM with the additional restrictions stated above may seem overkill, but the burden pays off in the end. Having a clear distinction between Blocks and Elements simplifies the way we think about components, lets us avoid a lot of styling problems, and gives us a clear vocabulary to use when we discuss or review code.

--

--