Migrating to CSS in JS: Part 1

Rui Duarte
Plum Guide
4 min readJun 28, 2021

--

Part 1 of a series of posts highlighting some of the motivations and decisions that led us to our recent endeavour into CSS in JS frameworks.

We will take a deep dive into our current stack and the problems we faced when scaling our design system and component library.

This post will highlight the problems we faced at Plum Guide and why we took this approach. Not every solution applies and viable to every situation.

The current stack

We are currently using CSS preprocessors, using SCSS and BEM as a naming convention.

This brings along some nice extendibility that plain CSS syntax does not support. At the time of writing this article here are a few examples:

  • Nested style rules
  • Mixins
  • Complex variables and manipulation
  • Built-in colour functions and more

While the CSSWG (CSS Working Group) has been consistently improving the feature set of vanilla CSS, some scenarios still require different approaches.

For the last 2 years we have been building a comprehensive component library in Storybook that has allowed us to build consistent UI faster.

As our design iterates and we enhance our UX, we naturally found some components would require some variants or modifications (modifiers as BEM references them by).

For example, a component as simple as the Button can have many different variations for size, appearance and behaviour. Common ones are primary and secondary for appearance and small and large for size.

Using the BEM naming convention, would be something like the following:

Consider a scenario where we only use the large primary Button. Ideally, we would only want to import the necessary styles for that variation. However, it’s not easy to effectively understand which styles should be sent to the user’s browser when importing the CSS file.

This means that all users will end up receiving all the possible styles of each component.

The pains of preprocessing styles

Bloated bundle

Since code-splitting CSS code is exceptionally tricky, we (and many others) were forced to ship the entire styles of each component. This, of course, has a direct impact on the performance of the site and can lead to a higher drop-off rate of our users.

Taking our homepage as an example, we can see that our main CSS file has a transfer size of around 21kb. Running a Lighthouse audit on the page shows that we can potentially have savings of up to 90%.

Screenshot of Lighthouse audit result for unused CSS

Realistically speaking, achieving these savings would mean splitting our CSS by device dimensions since these calculations are measured against a specific mobile device (Moto G4).

We have performed some more accurate measurements, and we can estimate savings up to 80%, although this will vary depending on the project.

Poor loading UX

Another problem we encountered was FOUC (Flash Of Unstyled Content).

This is a common problem that most websites have where the stylesheets are bundled separately from the HTML (external CSS files), and so this leads to the page appearing unstyled while the styles are being loaded.

To avoid this, we would have had to split our CSS bundle intelligently and directly inject the critical/essential styles on a <style> tag and have the remaining be imported separately.

Example of FOUC https://github.com/vercel/next.js/issues/18769

Discoverability of styles

While code editors offer good support for .scss files, it adds overhead to our toolchain when trying to share styles across projects.

Without spending too much time, the furthest we got was to have our code editors index all of the classes present in our internal packages using the extension SCSS Everywhere.

This left us without an intuitive way to directly consume design tokens (colours, spacing values, etc.) or helper mixins from our design system and component library.

Composition

It’s certainly possible to use style composition in SCSS by using, for example, the @extend rule.

Although the outcome isn’t always what you would expect, you might end up having specificity issues with your CSS instead of safely extending styles.

If you’d like to understand more how it works and some use cases, this article by David Khourshid explains it quite nicely.

Quick recap

We went over what motivated us to look at alternative solutions to CSS preprocessors and the critical problems we were facing.

If you find yourself in a similar situation to ours, we suggest the following approach:

  1. Identify the core problems you’re faced with
  2. Research the market and understand which solution fits your needs the most (e.g. emotion, styled-components, Stitches or vanilla-extract)
  3. Start small and introduce it early to avoid any tech debt building up

Stick around for Part 2, where we will go in-depth on the market research we performed on CSS in JS frameworks and where we will share our choice.

If you’re like us and love to share your work with your peers, and interested in challenging problems and scalability, we are hiring!

--

--