Gaurab Paul

Polyglot software developer & consultant passionate about web development, distributed systems and open source technologies

Support my blog and open-source work

Tags

Embracing BEM methodology in React applications
Posted  8 years ago

This post has not been updated in quite some time and the content here may be out of date or not reflect my current my recommedation in the matter.

React and modular CSS

React provides an elegant component oriented approach towards structuring our User interface. However it is not very prescriptive of how to modularize the CSS. Many React core team members have opined that it is better to ditch css entirely and embrace js based inline styles. However that decision does not go well with a lot of frontend teams and makes things difficult for newcomers.

However there many popular approaches for modularizing CSS that have evolved from community experience independent of React. Once such approach is BEM which has proven to be quite popular and effective in practice.

This post outlines a few functional utilities to effectively and succinctly use BEM alongside React.

Composing class names

While ES6 template strings provide a decent solution for string interpolation, it becomes cumbersome when we assign/toggle many classnames based on props or state variables. JedWatson/classnames is a nifty functional utility that alleviates some of this pain. Some examples directly taken from the README illustrate the use case very well:

classNames('foo', 'bar'); // => 'foo bar'
classNames('foo', { bar: true }); // => 'foo bar'
classNames({ 'foo-bar': true }); // => 'foo-bar'
classNames({ 'foo-bar': false }); // => ''
classNames({ foo: true }, { bar: true }); // => 'foo bar'
classNames({ foo: true, bar: true }); // => 'foo bar'

// lots of arguments of various types
classNames('foo', { bar: true, duck: false }, 'baz', { quux: true }); // => 'foo bar baz quux'

// other falsy values are just ignored
classNames(null, false, 'bar', undefined, 0, 1, { baz: null }, ''); // => 'bar 1'

Eliminating redundancy in class names using CSS precompilers

CSS precompilers like SASS and LESS allow us to augment the parent context in included scope using ampersand (&) prefix. This significantly helps towards keeping our stylesheets DRY:

.block {
    background: white;

    &__element-one {
        border-bottom: 1px solid red;

        &--modifier-one {
            color: red;
        }

    }
}

Eliminating redundancy in class names in react components

bem-classname is a nifty utility that allows us to eliminate the redundancy while assigning classnames to DOM elements.

The canonical approach is to pass the block, element and modifier names to the function exposed by the utility:

bemClassName('block', 'element', ['awesome']); // block__element block__element--awesome

However the function can also be bound to a block context allowing us to strip away some of the boilerplate:

import React from 'react'
import bem from 'bem-classname'

const Home = () => (
  <div className={className()}>
    <h1 className={className('header')}> Lorefnon </h1>
    <div className={className('description')}>
      Full Stack Web Application Developer
    </div>
  </div>
)

export default Home

// Private:

const className = bem.bind(null, 'Home')

The above stateless component generates the following markup:

  <div class="Home">
    <h1 class="Home__header"> Lorefnon </h1>
    <div class="Home__description">
      Full Stack Web Application Developer
    </div>
  </div>

Not that BEM does not restrict us to one block/element per node. So a single node can represent multiple blocks. In such cases it is helpful to combine the aforementioned two utilities.

Compressing classnames in production

While BEM is very useful from a developer perspective as it eliminates the tedium of dealing with CSS specificity to a significant extent - however it has been crticised for the elaborate classnames which contribute towards increased size of HTML & CSS files.

If your use case really demands this level of optimization, then there are utilities like grunt-class-id-minifier which can compress these class names as a part of the build process.

The plugin generates a mapping file which we can consume in our components as a normal javascript module.

module.exports = (function () {
    return {
        "Home__header": "a",
        "Home__description": "b"
    };
});
import mapper from './map.js'

const _className = bem.bind(null, 'Home')

const className = (...args) => {
  const c = _className(...args)
  return mapper[c] || c
}