Picking the “perfect-for-you” CSS-in-JS solution

Picking the “perfect-for-you” CSS-in-JS solution Part 1

We’ve been developing decoupled websites for a few years now. While it was clear early on that we wanted to build them with the Drupal CMS in the backend and using GraphQL and React on the frontend, it’s taken us a bit longer to figure out our CSS workflow for those JavaScript apps. Which CSS-in-JS solution is best? In this 2-part blog series, we’ll broadly categorize all CSS-in-JS projects and then pick our preferred one using a CSS-lover’s criteria.

The simplest way to integrate CSS with JavaScript applications is a basic bundling solution. In your ES6 JavaScript file, you simply import the CSS file.

Your application’s bundling tool, e.g. Webpack or Parcel, will now bundle the CSS with your JS application. But note there is no other integration; you have to ensure that the CSS is optimized and in-sync with your JS during development.

However, from even a little research, it’s apparent that we could have some big wins from integrating our CSS more closely with our JS; think: chocolate AND peanut butter big.

So how do you pick the best CSS-in-JS solution?

I’m aware of at least 68 different CSS-in-JS solutions so, obviously, there are a lot of different opinions about which is best. Many CSS-in-JS projects give a hat tip to the seminal presentation by Christopher “vjeux” Chedeau, “React: CSS in JS”. In it, he outlines 7 specific “problems with CSS at scale” that Facebook was trying to fix or minimize. Many of these are the same problems that my team and I (a non-mega-corporation) have.

Unfortunately, after careful re-reading of the slides, I was shocked to see in the very first slide that Facebook defines “problems of CSS at scale” as being the problems “in a codebase with hundreds of developers […] where most of them are not front-end developers.” [emphasis mine] Say, what?

So not only do we have dozens of projects to choose from, but many CSS-in-JS projects are optimized to solve problems that front-end developers don’t have. At Amazee we have well-rounded teams that include front-end developers; everyone works together to solve problems so we never have backend developers floundering with CSS features.

Picking the correct project means carefully determining what problems we want to fix and figuring out how the API of a CSS-in-JS project solves those problems. In contrast to Facebook’s 7 CSS problems, as a front-end developer who likes CSS, the 6 CSS problems my team needs to solve are:

  • Separation of concerns by task (instead of by technology)
  • Locally-scoped CSS
  • Prevention of unused CSS (dead code elimination)
  • Removal of duplicate selectors needed for CSS media queries and pseudo-classes (making code DRY: don’t repeat yourself)
  • Shared variables between our JavaScript and our CSS
  • Fast front-end performance (both JS execution and browser caching)

However, evaluating all 60+ CSS-in-JS projects using our 7 criteria would be a monumental task. Fortunately, we can also group all of these projects into three basic API approaches:

  1. CSS as an object literal
  2. CSS as a template literal
  3. separate CSS file

This categorization will help us quickly figure what works best for us.

APIs using Object literals

The first group of projects use an object literal, which just means creating a JavaScript object with CSS property values whose keys are the CSS property names. The earliest version of these kinds of APIs converted these into inline style properties on the app’s HTML which meant they didn’t support media queries or CSS pseudo-classes. Oops.

Fortunately, nearly all of these projects have fixed that problem by allowing you to specify locally-scoped class names inside the object literal. Here’s what that looks like:

The above object defines .base, .primary, and .warning rulesets. As you can see, the syntax here looks almost identical to vanilla CSS. But with the added bonus of fewer semi-colons and way more quotation marks and commas!

If you like CSS, you will not like this group of CSS-in-JS projects. If you eliminate this group, you’ve just eliminated approximately 61 of the 68 CSS-in-JS projects. whew.

APIs using template literals

The next group of CSS-in-JS projects looks much better to the eyes of CSS lovers. This API uses ES6’s new template literal feature, which is just a fancy way of writing really long strings with backticks. In the example below the css`` syntax takes the really long string between the backticks and passes it to the css function. That css function is written in JavaScript by the CSS-in-JS project for the express purpose of handling template literals and providing its API for your JavaScript app. It sounds more complicated than it looks.

Some projects go a step further and provide functions that output your chosen HTML element with the provided CSS. In the example below styled.div`` is again a function that handles template literals, but it also outputs a div element with the CSS applied to it.

This kind of project has great appeal to CSS lovers. Because it is using JavaScript to apply CSS, it is essential that the CSS-in-JS library either highly optimizes its JS run time or uses a Babel plugin to replace nearly all its JS code with generated CSS. While the Babel plugin is better, it also introduces a potential gotcha; some usages of the library’s API are incompatible with the Babel plugin. More on this later.

APIs using separate CSS files

The last “group” is actually one of the oldest solutions and there’s only one project in this group, CSS Modules. I would link to that project, but its website is horrible at explaining what it is. Instead, I’ll show you an example.

The CSS file above is actually processed by PostCSS, the same family of projects that includes the automatic vendor prefix tool, autoprefixer.

In fact, CSS Modules is just a thin layer that applies your PostCSS transformations to your CSS files and then passes that to your bundler tool. If you use webpack’s css-loader or postcss-loader, you’re already using “CSS Modules”.

What kinds of transformations can PostCSS do? For the example CSS file above, I chose the postcss-functions and postcss-nested plugins to match the nested ruleset and JavaScript-as-CSS-value features of the other CSS-in-JS projects. But you can choose from a wide variety of PostCSS plugins. PostCSS’s autoprefixer will automatically add the correct browser vender prefixes to your CSS properties. And the Sass plugin will allow Sass syntax in your CSS file.

Simple, right? I just wish the CSS Modules website was clearer.

To be continued…

By categorizing CSS-in-JS project APIs, we were able to eliminate 61 of the 68 projects that only support the object literal syntax. In my next blog post, we will look at projects using the other two API choices, examine each of our 6 CSS problems in detail, and use these criteria to find my team’s perfect CSS-in-JS solution.

Given our wealth of CSS-in-JS choices, there is no one “perfect” solution; there is only what is perfect for you. While you may not agree with our final pick, I hope the process we outline helps you to find the perfect-for-you CSS-in-JS solution.