When I first started using BEM (block-element-modifier) early in my career, I distinctly remember how refreshing it was to have a system to name and assign semantic meaning to our otherwise esoteric CSS blocks. Almost immediately (once I understood the rules) it became easy to glance at some CSS and visualise the changes that will be applied to elements in their various states. Love it or hate it, something about its simple underlying principles stuck with me.
It looked something like thisโฆ
.my-button { }
.my-button.my-button__icon { }
.my-button.my-button--primary { }
Nowadays, most of us are using CSS-in-JS libraries like styled-components or emotion (which are fantastic libraries btw), but all of a sudden it seems like we forgot about the helpful methodologies we learned with BEM, OOCSS and SMACSS. As a result CSS-in-JS that you encounter in the wild is hard to read and reason about.
You might be familiar with seeing code like this:
styled.button`
background: ${props => props.primary ? "you" : "didn't"}
color: ${props => props.primary ? "read" : "this"};
font-size: 1em;
margin: 1em;
`;
In this case, properties for the primary
modifier are computed individually, carrying an implicit runtime cost, which scales poorly as more modifiers are eventually added. More importantly, this carries substantial cognitive overhead for future maintainers, trying to understand how and when properties are being applied. A point proven by the fact that you probably didnโt read that code block at all (Check again ๐).
Now youโre the next developer to come along and attempt to add a disabled
state to this button. You might be inclined to continue this pattern and do something like thisโฆ
function getBackgroundColor(props) {
if (props.disabled) return 'grey';
if (props.primary) return 'blue';
return 'white';
}
function getColor(props) {
if (props.disabled) return 'darkgrey';
if (props.primary) return 'white';
return 'black';
}
styled.button`
background: ${getBackgroundColor};
color: ${getColor};
font-size: 1em;
margin: 1em;
`;
But this only further exacerbates the problem by creating yet another layer of indirection.. OH NO ๐ฑ Not only do you have to compute this function in your head, you now have to locate these helpers ๐คฏ
For better or worse styled-components is totally unopinionated about these things, if youโre not careful you might inadvertently allow bad practices to propagate through your components. Sure, you could BEM-ify this code in styled-components, but my point is that youโre not forced to by the API. Even so, BEM-like methodologies are no better because theyโre merely a set of rules and rules are great only until someone breaks them ๐ฎโโ๏ธ!
CSS-in-JS actually provides the perfect opportunity for an API abstraction to solve this very problem ๐ by abstracting away the messy details of a methodology and leaving you and your colleagues with a library that guards you from these implicit issues.
This was my motivation to build Trousers ๐ (v4 coming soon)
๐
but Iโm not the only one thinking about this! New libraries like Stitches are popping up all over the place, taking a similar approach to shepherding users into using good patterns through API design. Leaving us with the best of both worlds!
Trousers as an example provides grouped properties via modifiersโฆ
import css from '@trousers/core';
const styles = css('button', { backgroundColor: 'blue' })
.modifier('primary', { backgroundColor: 'white'})
.modifier('disabled', { backgroundColor: 'grey' });
Named modifiers controlled via propsโฆ
/* @jsx jsx */
import css from '@trousers/core';
import jsx from '@trousers/react';
const styles = css('button', { backgroundColor: 'blue' })
.modifier('primary', { backgroundColor: 'white'})
.modifier('disabled', { backgroundColor: 'grey' });
const CustomButton = (props) => (
<button
css={styles}
$primary={props.isPrimary}
$disabled={props.isDisabled}
/>
);
Themes as css variables, allowing for even less dynamic css and runtime cost.
css('button', { backgroundColor: 'blue' })
.modifier('primary', { backgroundColor: 'var(--color-primary)' })
.theme({ color: { primary: 'red' });
And all of the examples above will only ever mount 1 + number of modifiers
, regardless of component state and active theme.
All possible because CSS-in-JS provides us with layer of abstraction to do this work!
So my ask for you to takeaway from this blog is not to necessarily use my library, but start to think the cognitive science behind how we write CSS-in-JS today and how you can start incorporate these principles into your apps and libraries in the future to improve the readability and maintainability of your code!
Quick aside: Trousers is simply standing on the shoulders of other great libraries, so full credit to the people and libraries that inspired it!
Please do yourself a favour and checkout these fantastic libraries if you havenโt already:
Thanks for reading ๐
Top comments (6)
Readability depends on the reader (what reader got used to, what knowledge they has). Some people may say that,
is more readable than
as well other people may prefer something totally different, like tailwindcss ๐คทโโ๏ธ.
Is there a way to objectively check which of those more readable?
It's 2020 and I still don't understand the appeal of BEM. What's so difficult about writing and reading plain CSS rules without an over-reliance on classes?
I'm not sure I understand your question. The article was addressing this precise point? Unless I'm missing something? Did you disagree with the premise of the article, also?
The methodology described in the article is precisely about the opposite: make a class for every little thing. Why use
button
if you can use.my-button
instead, and then create lots of utility-classes.Oh not quite my intent, sorry if it was unclear! I'm suggesting to split things out into variants/modifiers in your code as opposed to calculating specific properties individually, something that i'm seeing a lot in css-in-js land. That might not necessarily mean create a class for everything, using annoying naming methodologies. In the case of CSS-in-JS there might be APIs to help you do that, abstracting the annoyance away and leaving you with the benefits these methodologies provide.
This was a cool take, I never made this connection myself between BEM / CSS-in-JS, thanks for the thoughts Daniel ๐