reBEM: React ♥ BEM
It’s been about a year since me and Kir Belevich started using React at Lazada Group for some internal admin interfaces. We learned a lot during that time and our minds have been heavily shifted by that one-way data-flow philosophy React pushes.
Since React is not exactly a framework, but just a view library, we have both freedom of choice and a burden of responsibility, like: how to structure your application, how to manage its state or how to style your components. And styling was one of the first problems we were trying to address.
Problems with styling
There are many issues with styling and they go far beyond React ecosystem, but there are two problems we wanted to resolve the most: naming inconsistency and style isolation.
“There are only two hard problems in Computer Science: cache invalidation and naming things.” — Phil Karlton
That’s true. And often people don’t really bother. They name things half-randomly, as they want. In small projects it might not be that much of an issue, but when things get bigger, messy naming leads to readability and maintainability problems especially when you’re working in a team.
The same goes for the lack of style isolation in components: the more your application grows, the more there is a chance that things will go out of control and some unpredictable nasty side-effects might be introduced. Name collisions, unexpected style overwriting, !important and huge cascade chains, etc. — you know what I’m talking about.
Inline styles were not exactly our piece of pie for many reasons. CSS-modules were not yet invented back in Jan 2015, but even now we are not very happy with theming and a complete lack of cascade (which can be used to apply context-specific styles to the internal elements of a component). So, eventually we were left with the solution that already works for many people: naming convention, in particular, BEM.
React ♥ BEM
“BEM is a methodology, that makes your front-end code reusable, scalable, more strict and explicit.” — getbem.com
As you probably know, the idea of BEM is that you have a certain naming convention that helps you modularize your styles and isolate them while keeping flexibility and convenience of the good old CSS. Some people might argue that isolation is not exactly bulletproof, but from our experience and from the experience of some big companies like Yandex (they built their entire front-end stack around BEM methodology) it’s rarely the case in a real practice.
Element, modifier and mix are also easy to read, understand and they are already familiar for a lot of people. So, thanks to BEM abstractions you don’t really have to think about naming too much, because you already have guidelines you can follow.
So how do you BEM in React?
There are lots of other things people use, but what we didn’t really like about all those solutions is that you still have to explicitly construct classNames with awkward helpers. Since we already have BEM abstractions — why not just use them directly?
Using normal js instead of ugly jsx was really appealing, also at that point of time we were really excited that we could do inheritance (ES6 classes + modifying BEMJSON in render). We shipped a couple of projects with this tool and it worked fine, but eventually we decided to dump this idea for two reasons.
Patching React leads to maintainability hell.
In order this whole BEMJSON thing to work we had to wrap React’s top-level API. It lead to maintainability problems and some minor performance issues. Also because we didn’t really use React directly, we had to wrap our components before working with external solutions like React Router which was not nice at all.
Inheritance is not such a good idea.
While inheritance might look really good at first sight (because DRY and stuff), it can create some maintainability problems as well. The more your codebase is growing, the harder it becomes to not to break anything and cause awful side-effects. And even if you’re trying to follow semver, you will have to bump major versions with every tiny change of your components internals.
So, we’ve thought it through and simplified everything. A lot.
reBEM is a set of independent decoupled packages for working with BEM in React ecosystem. Let’s start with reBEM package itself. This is how our popup from the previous examples looks like:
But if you use and love JSX, we’ve got you covered too with a small Babel plugin. The same popup in reBEM JSX:
If you use external components you might want to “BEMify” them like this:
Also the code above is a great example of how great functional composition works — no more unpredictable side-effects of inheritance. Existing Modal component is rendered inside Popup component and we’re working only with an external API, not knowing anything about Modal’s internals.
reBEM test-utils and enzyme addons
While we completely abstracted away from className in render, we figured it would be great to have something like that in our unit-tests. We already used Chai BEM assertions for some time and we also created a couple of testing addons for React Test Utils and Enzyme testing utility from Airbnb:
Kir Belevich had this great idea of abstracting away from classNames in CSS too, so reBEM PostCSS plugin was born. Take a look at the example:
It’s an absolutely valid CSS, because these are just pseudo-elements, so highlighting in text editors and IDEs would not be broken. And you can use it in vanilla CSS or with any preprocessor.
With reBEM layers you can:
- share and compose the entire component libraries
- easily create themes
- concentrate on app functionality, not components
There are endless ways reBEM layers can be used and organized, so the example I’ll demonstrate you is just one of the many options. Here is an illustration for a better understanding.
- The first layer in the bottom is core-components where we have all those form elements and basic components like popup and tabs — these are the components that will likely to be in ANY application or interface. We don’t have any styles in this theme, so it’s only about functionality and structure.
- The next one is reset theme — often we need to reduce browser inconsistencies and give the same look for all html-elements on the page — so we can just apply this theme. Some people prefer resetting locally, that’s why it’s separated and not included in core-components by default. This theme contains only styles.
- Now, custom components. This layer can contain a lot of specific to your company/product stuff — Calendars, Notifications, widgets, some modifications of core components, etc.
- And finally, we have an application layer with some specific components that are not shareable outside the app. So, now when most of the components are in the layers, you have a very short list of app components and you can concentrate on its functionality and domain-specific problems.
You might notice that we go from less specific layers that can be shared widely to the more specific where sharing is limited to some company’s product or not shareable at all like in the application layer.
Another example is platform layers. It’s pretty much self-explanatory:
Components from layers are imported with a special #-character along with their styles. So instead of:
you just write:
It imports component from the nearest layer and styles from all layers.
Example: a history of one button
If it’s a bit confusing at this point, let’s clarify things by looking at the specific usage example: a good old button.
We’ll start at the core-components layer where components are as basic and simple as they can be. This is a button with a wrapper to be able to add children to it. Nothing fancy. Notice that we provide the ability to set mods and mix through props — it’s a good pattern allowing you to customize components from outside.
Now, we reset styles for this button. In this layer, we have only styles, since we don’t need to add anything to the components logic.
Now we are in the layer where we store some custom components, and we want all buttons in our apps to be able to have an icon. Let’s do it by simply adding an optional Icon child if there is an icon prop.
Note that we take all styles from the previous layers (only reset theme so far) and the Button from the last layer which is core components and then we use this button in our new component. This is the power of composition — see, we don’t CHANGE anything in a button from core-components, we just use it to create more complex button.
Now to the product theme with our company colors, logos and icons and stuff. To be able to style our button, we add another element called mask to the existing children. And then we also add some styles to this element.
So now the Button from the previous layer is custom-components, but styles are imported not only from reset theme, but also from our current product-theme. Again, we don’t change anything in the original button, we just use it to create another component.
And finally we are in the application where we compose all the layers and add app layer where we can have some additional logic.
The same picture: Button is from the last layer (product theme) and styles from all previous themes. Rick and Roll!
reBEM layers: webpack
So far we did implementation only for webpack, but theoretically it can be implemented for other module bundlers as well. Here is what rebem-layers-loader config looks like:
You specify shared layers, your app local layer (with files you use in your components) and then any places where you plan to require components from your layers (app src or unit-tests directory).
More on how to configure and share your layers is in the official documentation.
While some people use inline styles and css-modules, for others these solutions might not be suitable. So it’s nice to have a middle-ground alternative — easy to grasp and familiar, but in the same time successfully solving style isolation and naming inconsistency problems.
Try it out
There is a starter-kit you can use to take a look at how different parts of reBEM work in a simple application. Also it can be a good starting point for your own projects.