DEV Community

Daniel Del Core
Daniel Del Core

Posted on

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 ๐Ÿ‘‹

Top comments (6)

Collapse
 
stereobooster profile image
stereobooster • Edited

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
๐’ŽWii ๐Ÿณ๏ธโ€โšง๏ธ

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
 
loujaybee profile image
Lou (๐Ÿš€ Open Up The Cloud โ˜๏ธ) • Edited

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
๐’ŽWii ๐Ÿณ๏ธโ€โšง๏ธ

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 • Edited

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 (๐Ÿš€ Open Up The Cloud โ˜๏ธ)

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