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
}