CSS-in-JS: What happened to readability?

danieldelcore

Daniel Del Core

Posted on November 6, 2020

CSS-in-JS: What happened to readability?

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 👋

💖 💪 🙅 🚩
danieldelcore
Daniel Del Core

Posted on November 6, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related