Valtech Design
Published in

Valtech Design

Why I Dislike Existing CSS-in-JS Solutions for React 💅🚫

I really love CSS/Sass. These days, despite the title of this article, I actually prefer using JavaScript for styling. Unfortunately I just don’t really like any of the existing solutions. In this article I will go through some of the most popular CSS-in-JS solutions and explain my dislike for them (it will be my first time using some of these properly, I’m just pessimistic). Generally, I’m not too hung-up on their actual “features” per se (I naturally assume I’m able to do what I need to do), I’m more concerned about the how — the APIs, DX and learning-curves involved, coming from a Sass background.

The solutions I will be looking at are as follows (I picked the most popular ones from this list):

I will start with a base, styleless UI component using React, which I will then attempt to style using the above solutions. I’ll say right now any solution that requires me to radically change the code from how it currently is will lose a lot of points (this is mostly because I’d prefer my styling solution to not affect any other paradigms).

I’ve opted to not add any CSS class names for now, because without any mapping to any CSS, there’s not really any point. I’d normally use BEM as a CSS naming convention. Of course, this does mean it currently won’t work — whilst we have a nice internal state to determine if the panel is open using the useState hook, without any styling, this is useless. I’m going to attempt to use the proposed tools to map the above state to some styling (perhaps in the form of a CSS class name?) to get it working and looking swish.

Aphrodite

View demo on CodeSandbox

The first reason I don’t like it, is because passing a function that seemingly returns an object to a className parameter doesn’t make sense to me…it’s a hack at best; className intuitively should be a string, not an object.

The second reason I don’t like it is because it doesn’t play nicely with stylistic modifiers like open (to correspond to the isOpen state). I would ideally like to keep all my panel styles together, not as separate objects for each state (i.e I’d prefer to be able to do heading.open instead of headingOpen.

Finally, I somewhat resent having to increase the length of each line which contains an element I want styled; rather than having inline conditions to determine which objects to pass, I preferred the simplicity of string class names. I could even go for a function which accepts the isOpen value and then determines which object to apply internally or something, but alas…

These may seem like trivial nitpicks, but remember that I’m a fiend for DX and idealistic APIs. I could perhaps forgive any one of these gripes, but combined they’re enough to deter me from using it. The thing I do like about this solution is the scoped styles that are generated.

Emotion

View demo on CodeSandbox

I think I prefer this way to the previous way — it’s more natural to me coming from a Sass background. I opted for object styles instead of string styles because I can’t stand tagged template literals. Another reason I like it is because I am just mapping one object to my JSX — the logic to find and apply styles lies with the tool based off class names (just like CSS).

The first thing I dislike is that it didn’t work how I wanted/expected. I wanted to be able to do this:

…but it seems like putting the ampersand (&) after the selector doesn’t work (which does work in Sass). Also, I have to duplicate the heading hover styles to get the purple background color. Other than using !important or a CSS naming convention like BEM (which defeats the point of doing CSS-in-JS) I’m not sure how else to write it.

The second thing I dislike about it is that I either need to add /** @jsx jsx */ to the top of the file (seems hacky), I or have to use a babel-preset (not the end of the world, but not ideal)…

The final thing I dislike (and have always disliked when manipulating class names with JavaScript) is the {'panel' + (isOpen ? ' open' : '')} part, specifically the conditional space between the class names. This is very nit-picky at this point but to an untrained developer, it looks like a typo and is hence not intuitive. I anticipate this gripe being a recurring one throughout the following demos. The only other alternative would be to create separate styles objects for each state, thus rendering one of the reasons I like this approach redundant. The obvious work around for this issue is to use something like the classnames package, which I would be happy to do, if this were my only issue with this solution.

Glamor

View demo on CodeSandbox

There’s really not much difference between this and the previous examples from what I can tell. I no-longer have to add /** @jsx jsx */ at the top, but the styles are riddled with more &’s, but it more or less feels the exact same. Plus it doesn’t look like the project has been updated in over 2 years so it’s perhaps on it’s way out? Maybe I missed the memo.

CSS-Modules

The main reason why I like CSS-Modules is because of the scoped styles. This means I don’t need a naming convention like BEM, or have to worry about styles polluting the global namespace. The first thing I dislike about it, however, is the same thing I dislike about Aphrodite, and that is not being able to keep all related styles within the same parent object (i.e no nested selectors); I have to do things like headingOpen instead of heading.open, which for me causes some points to be deducted.

The second minor gripe I have is that styles have to be kept external within a CSS file, essentially making this solution more of a CSS importer. This isn’t necessarily a bad thing; it enforces separating styles into their own file which is a paradigm I fully condone, but when thinking about CSS-in-JS, I would expect the flexibility of my JavaScript styles to be equal to actual JavaScript, meaning I’d like to choose to be able to import them from an external file or keep them in the same file as I’ve done with the previous examples. The only real benefit I can see of CSS-Modules is scoping (which is still a good benefit, nonetheless), but isn’t really much better than Sass + BEM from a DX perspective.

The main thing I dislike about CSS-Modules, however, is the inability to pass data from JavaScript to the CSS-Module (though the inverse is possible). One of my initial motivations for using CSS-in-JS was the desire to easily share data between my JavaScript and CSS. Also, and take this how you will, CSS-Modules has so far been the first solution that requires me to edit my Webpack configuration. Because of this I’ve been unable to create a demo on CodeSandbox.

Fela

View demo on CodeSandbox

The first issue I have with Fela is that is requires me to provide my styles to a wrapper component (the provider). This doesn’t sit right with me as I don’t really like introducing new components into my proverbial markup whose semantic purpose is to provide things like styling, I would prefer to pass props to my existing components/divs, and even more ideally just pass a single prop to the outer most component/div. Plus, I don’t really understand what this “provider” or “createRenderer” are doing. I’m a CSS guy remember, these just seem like voodoo to me.

However, I do really like the state based paradigm — I can pass the isOpen state to the styles function and then determine which styles to render, rather than conditionally passing different style collections in the JSX. I believe the styles’ have responsibility to provide this logic. “State” in CSS is normally derived from class names, so I resonate with the Fela paradigm as testing the isOpen parameter and rendering different styles based on the value of this parameter is really no different to testing the presence of an isOpen CSS class (&.isOpen or .isOpen &)in Sass). I also like being able to keep my styles organised nicely (doing styles.heading is so much nicer than headingStyles).

So whilst Fela is probably my favourite solution so far, I still take issue with things like having to define a renderer and wrap my component in a provider. Also:

className={renderer.renderRule(styles.title, { isOpen })}

…semantically, I’m not really passing a className here, and this causes the lines to become too long for me to consider it the ideal solution. Of course, if I did end up using Fela I would probably create a HOC to allow the above to become something like:

styles={[styles.title, { isOpen }]}

…which is a much nicer API, but would come with an overhead of having to create the HOC. But to reiterate, Fela has the least off-putting gripes for me of all the solutions looked at so far, and has been the only solution I could see myself using (*so far*).

JSS

View Demo on CodeSandbox

And just like that, my head is turned. This is by far my favourite solution so far. It uses version 10.0.0-alpha.21 of JSS, to get the new useStyles Hook API. It allows selector nesting (which I have taken advantage of in the above code with .isOpen &), which means I don’t have to conditionally pass different objects in the JSX (though I did prefer how this was handled with Fela by passing state). The classnames generated will be unique so there’s no issue with global scope clashes (the only global scope classes used will be for things like state, such as isOpen, I feel like this is probably quite safe).

I really don’t have many complaints about this solution; passing an object to the className attribute is again a minor gripe I have (…semantics), but is one I could live with. Emotion and Glamor had DX benefits of only having to pass/map one object to the JSX, whereas above, any element I want styled needs to have direct mapping to the useStyles object — this isn’t a massive issue, but it does mean my JSX is more tightly coupled to the JSS library (the JSX when using Emotion compared with Glamor is identical). Also Fela had a better DX I feel when it comes to mapping the state to styles. Using JSS I’m mapping the isOpen state to an isOpen class on the parent div which I then read in the JSS. It’s pretty good, but if the createUseStyles allowed the passing of an object (for state) instead, I’d prefer that.

ReactCSS

View demo on CodeSandbox

ReactCSS makes uses of props and state, which I like, except that in order to do so, you are expected to define your styles within your component’s render method (or inside the function if using functional components), which I don’t like. In order to circumvent this, I had to create a wrapper function that accepts the props and state (which I call useStyles to mimic React Hooks, which kind of feels like cheating as I could have done this for some of the other solutions to improve their experience as well). I also like the ability to easily group multiple styles by state —it offers some good flexibility. But since I have access to the state, I’d sooner do something like this:

…which kind of makes the whole default key redundant, and feels like it misses the point of ReactCSS. Plus, if you didn’t already notice, I also couldn’t figure out how to get hover working properly for the heading components, the API for hover seems pretty whack. But I’m definitely getting closer to finding that ideal API.

Styled-Components

View demo on CodeSandbox

Now the first thing is that it uses tagged template literals, which I can’t stand, but that’s a relatively easy work around by converting the Heading and Content elements to:

I realise this is personal preference, but I prefer it this way (I can also add multiple styles based on prop values this way). I also opted for adding an isOpen class to the parent div so I didn’t have to add props to both heading and content, forcing me to do with something like:

background-color: ${props => props.isOpen ? 'blue' : 'red'};

…don’t get me wrong, the prospect of accessing props in the styles is fantastic, but when the isOpen prop really belongs on the parent element, I’d rather not pass it to all child elements in the JSX (a recurring theme in this article is how I prefer to pass the isOpen prop to just one element — the parent element)

Now that we’ve got that out the way, I can begin the other criticisms. I do like my JSX having Heading and Content components instead of divs, but if I were keeping my styles in the same object (which I would like to), I would end up with something like <styles.heading> instead of <Heading> as my component. Highlighting it in this light shows how the paradigm just doesn’t make sense to me. Styles should be passed to a prop of some component, not be the component.

However, this solution has certainly landed me with the cleanest JSX so far, but I just don’t really get styled-components as a philosophy — it seems a bit too radically different to existing conventions without gaining any radical benefits. But it’s certainly not my least favourite approach.

Styled-JSX

Unfortunately I couldn’t get a live demo of Style-JSX working because it requires configuring Babel. Also, without the styled-jsx-plugin-sass plugin, I wouldn’t consider using this solution — and even with it, I doubt I would. Whilst I do like the fact that the generated CSS is scoped (making the solution clean and safe), the styles do have to be written within the <style> tag; you can’t pass a reference to string template literal, for example, which means you can’t keep your styles in an external file (the opposite problem to css-modules). I also had to duplicate the heading hover styles to get the purple background color. It’s not the worst solution, but even with the addition of styled-jsx-plugin-sass, given the other shortcomings and overheads involved I’m not sure it’s the solution for me.

Other Notable Mentions

  • linaria — this seems like a good alternative to styled-components, but doesn’t seem to be quite what I’m after.
  • CSS-Blocks — this looks really interesting but I’m not convinced I can get a friendly and flexible API and I’m not sure the benefits are any better than the other solutions I’ve looked at.

Conclusion

After having looked at all these solutions, I’m confident that my ideal solution would not have:

  • Requirement of dedicated provider component
  • Conditional passing of different style objects in the JSX
  • Global CSS namespace concerns
  • Overly long lines on components that require styles
  • Forced separate grouping of styles for various states (styles pertaining to state variations should have the ability to be nested within the parent object)
  • Requirement of modifying Webpack/Babel configuration

…and my ideal solution would have:

  • Friendly way to map state to styles
  • Resemble JavaScript more than CSS (without losing important CSS principals) — I guess I dislike things like '.isOpen &': {...} strings in my JavaScript as it’s not really transparent what’s going on unless you’re familiar with Sass — I prefer ...(isOpen && {...}, which is just plain ES6 JavaScript
  • Single mapping of styles object to parent JSX tag to reduce tight coupling of styles technology to the JSX

All of these things have been witnessed throughout the various solutions, but none of them check all the boxes. With all this in mind, I can probably now attempt to wireframe my ideal API:

Let’s go through each aspect and discuss why I feel this API is better than any API achievable from the existing solutions we’ve looked at.

  • It has the benefits of a provider by passing the styles to the JSX once on the parent element — this parent element uses a <Module> tag to highlight that it is a parent element to which styles can be passed (other suitable names might be things like Block), but in this case, the provider (<Module>) also renders a div to the DOM.
  • States such as isOpen are passed as props to elements (instead of CSS classes) — which are marked up using <Component> tags and passed name attributes (instead of className or styles etc) — this provides the same benefit I liked from styled-components where my components were viewed as Heading and Content, except I don’t have to create new constants for them — I just have Component tags with name attributes.
  • …this ensures my JSX is clean and semantically makes sense.
  • Styles for each element/Component exist in a single object as functions which expose a context argument; this is to be thought of as any parent state, such as the isOpen prop on the parent panel component. From the JSX, we can infer that when the panel Component has an isOpen prop with a value of true, then the child heading and content Components can be considered to have a context of isOpen.
  • In addition to context, the functions can also expose the components own props (which can be thought of as their state )— so in the case of the panel component, this would be considered to have a state of isOpen, which in turn provides a context of the same value (isOpen) to child components (heading and content).
  • …essentially, ...(context.panel.isOpen && {...}) can be thought of as the ideal JavaScript equivalent of .panel.isOpen & {...} in Sass (they actually look more similar than they are…the &’s have completely different meanings in both, but I digress…), and ...(state.isOpen && {...}) can be thought of as the ideal JavaScript equivalent of &.isOpen {...} in Sass (if these comparisons don’t make sense, don’t worry about it…).

Putting all this together could leave us with some styles object resembling:

…to reiterate, state refers to the props on the component itself (which should be applied based on some actual state, as per the example), and context refers to props on parent elements.

Hopefully by comparing and contrasting the proposed ideal API with the existing solutions you can understand why it makes sense. It’s really just a combination of the best bits from all the other solutions, from an API/DX point of view. When you strip away the tools and technologies that dictate how you should do something, and instead focus on principals and philosophies, you end up with a result that makes much more sense. This is why I’m such a big fan of API-Driven-Development.

Enter: Lucid

View the above idealistic API as a working demo on CodeSandbox

The proposed ideal API is actually real code, made possible by the Lucid Library, and it’s actually quite simple how it works:

Inline Styles vs CSS

Something I haven’t touched upon this article is whether or not a solution produces real CSS, or whether it produces inline-styles. The reason I haven’t touched upon this is because I’m not convinced that any discrepancies in performance are ever going to be noticeable to end users (and performance is the only issue I’ve seen people raise with inline styles). If we can take this out of the equation, I can then focus my efforts when choosing a solution on APIs and DX. Taking performance out of the equation, I actually prefer the concept of inline-styles over CSS, as they make more sense when using JavaScript, and I’d like to think the performance of using JavaScript to apply styles is good enough, seeing as it’s a standard API and has been since, like, forever.

Further Reading

If the ideal API I proposed also excites you like it does myself, then you may find some benefit in checking out these others resources as well as the Lucid library:

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Edmund Reed

Edmund Reed

153 Followers

Design Systems Architect 🎨 UI•UX designer & developer 💻 I take front-end thought experiments too far 🧪 @valtech 💙