loading...

CSS-in-JS: What happened to readability?

danieldelcore profile image Daniel Del Core ・4 min read

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 { }
Enter fullscreen mode Exit fullscreen mode

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;
`;
Enter fullscreen mode Exit fullscreen mode

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;
`;
Enter fullscreen mode Exit fullscreen mode

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' });
Enter fullscreen mode Exit fullscreen mode

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} 
  />
);
Enter fullscreen mode Exit fullscreen mode

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' });
Enter fullscreen mode Exit fullscreen mode

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 👋

Discussion

pic
Editor guide
Collapse
jfbrennan profile image
Jordan Brennan

CSS-in-JS was never readable and it was never going to be because React is mixing two very different languages together (plus JSX smh). This is one of the many things I think Vue, Riot, Svelte got right. They just leverage <style></style> and the whole CSS-in-JS thing goes away. Too easy, mate!

Collapse
stereobooster profile image
stereobooster

Readability depends on the reader (what reader got used to, what knowledge they has). Some people may say that,

styled.button`
 background: ${props => props.primary ? "you" : "didn't"}
 color: ${props => props.primary ? "read" : "this"};
 font-size: 1em;
 margin: 1em;
`;
Enter fullscreen mode Exit fullscreen mode

is more readable than

css('button', { backgroundColor: 'blue' })
  .modifier('primary', { backgroundColor: 'var(--color-primary)' })
  .theme({ color: { primary: 'red' });
Enter fullscreen mode Exit fullscreen mode

as well other people may prefer something totally different, like tailwindcss 🤷‍♀️.

Is there a way to objectively check which of those more readable?

Collapse
darkwiiplayer profile image
DarkWiiPlayer

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?

Collapse
jfbrennan profile image
Jordan Brennan

Same. I saw BEM back when and thought, "Nooope." I prefer custom HTML tags with utility style classes:

<m-icon name="phone" class="pad-all-lg txt-lg"></m-icon>
Enter fullscreen mode Exit fullscreen mode

Like Custom Elements, but no JavaScript needed. Some of the styles are unique to the component (the custom m- tag), but more styles can be added with utility classes. A good balance between meaningful component API and flexible styling imo

FWIW I have a whole library m-docs.org

Collapse
loujaybee profile image
Lou — Cloud Engineer

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?

Collapse
darkwiiplayer profile image
DarkWiiPlayer

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.

Thread Thread
danieldelcore profile image
Daniel Del Core Author

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.

Collapse
loujaybee profile image
Lou — Cloud Engineer

This was a cool take, I never made this connection myself between BEM / CSS-in-JS, thanks for the thoughts Daniel 🙏