EXPEDIA GROUP TECHNOLOGY — SOFTWARE

CSS-in-JS: An Investigation

An introductory comparison of popular CSS-in-JS libraries

Paul Corbett
Expedia Group Technology

--

A stack of books on JavaScript and CSS with a phone on top
Photo by KOBU Agency on Unsplash

TLDR: Using a Gatsby starter setup I investigated four CSS-in-JS libraries, Emotion, styled-components, Treat and JSS. In this comparison I summarise the differences in setup, documentation, styling paradigm and Node package size. The libraries are all very similar and essentially end up doing the same thing, but in different ways. Treat has the smallest bundle size with JSS the largest. Styled-components is the most popular with Treat the least. Using styled-components is the safest way to get started, but, Treat may be worth a gamble based on its scalability and bundle size.

The hack

HTML in JavaScript, are you sure? CSS in JavaScript? Now I know you’re having a laugh!

A few years ago if someone mentioned writing HTML in JavaScript as a way of building large scale websites, I would have laughed it off as some sort of crazy idea. Now, with React, writing markup as part of a component’s render function is second nature.

For me, it has been the same with CSS. I heard about the move to code CSS into components a while ago and the thought made me cringe. Surely, these are completely different languages and mixing them is just asking for trouble.

However, recently I’ve taken more of an interest. During a recent hackathon, I experimented with four libraries I came across:

  • Emotion
  • Styled-components
  • Treat
  • JSS

Findings

Emotion (64.82kb)

Emotion is a library designed for writing css styles with JavaScript. It provides powerful and predictable style composition in addition to a great developer experience with features such as source maps, labels, and testing utilities. Both string and object styles are supported.

Setup

Straightforward installation with optional extras for styled CSS and React (which are separate packages). The core package gives developers access to the css prop to add styles.

Optional extras include @emotion/styled package allowing components to have styles attached to them.

Emotion has an optional Babel plugin that optimises styles by compressing and hoisting them, creating a better developer experience with source maps and labels.

Adding a theme was straightforward with a basic JavaScript object passed as a prop to the ThemeProvider.

Documentation

Good documentation with easy to follow steps to get up and running. The documentation is separated into sections for beginners, advanced, tooling and plugins.

Style

As you can see from the code above, Emotion can be used in two different ways, directly as CSS on an HTML element or by attaching styles to the component. For me, both have their weaknesses. The base css prop is obviously a new addition but it doesn't allow for reuse. If two elements share some common styling there doesn't appear to be a way to extend a base style, unlike how it can be done in styled-components.

One of the examples was the following:

This to me, seems unmaintainable and very difficult to read.

Also depending on how you prefer naming, styles can either be written as camelCase or when importing css function from the core package you can use familiar CSS selectors.

Attaching styles to components takes the HTML element out of the React component and into the styles file. This took some getting used to. I found importing all the styles back into the component could mean a lengthy import statement.

Styled-components (92.47kb)

Utilising tagged template literals (a recent addition to JavaScript) and the power of CSS, styled-components allows you to write actual CSS code to style your components. It also removes the mapping between components and styles — using components as a low-level styling construct could not be easier!

Setup

Easiest of the four to get setup as its one package that works straight out of the box (though a babel plugin require for Server-Side Rendering.) As with Emotion, adding a theme was also simple with a JavaScript object passed as a prop to a theme provider.

Documentation

Very good docs with easy to follow setup with an additional section on API references.

Style

Styled-components focus more on exactly that - styling the components.

Styles can be adapted by passing in props to those components. So for example if a button is clicked, an internal state can change which in turn alters the props passed to the styled component.

An advantage I see with this package compared to Emotion is the ability to extend styles.

Like Emotion, importing all these styles back into the React component seemed lengthy.

If moving to a CSS-in-JS library, something written with familiar CSS naming structure is easier than remembering to switch to camelCase.

Treat (2.67kb)

Themeable, statically extracted CSS‑in‑JS with near‑zero runtime.

Setup

Basic setup with one package. An additional package is required if theming is required. Gatsby didn’t require this, but under normal circumstances, a webpack plugin needs to be added to the config (this is included in the core package.)

Documentation

Treat was built by the team at Seek Australia so its purpose fits their needs but they have been kind enough to open-source the library. As with the others above, the documentation was easy to follow and includes some history into why it was created over using an existing package. There is a section on the styling API which gives access to a few helpful functions such as styleMap which allows you to easily create multiple namespaces within a Treat file.

Style

By default all styles need to be separated into a file with .treat.js / .treat.ts extensions. This is so they can be compiled and executed at build time.

Then they can be imported into a React component:

This is where I started to see some differences. Styles are written in camelCase in an object and are added to elements using the familiar React className (classname packages can also be used to bundle classes.) This leaves HTML elements to stay within the familiar structure in component render functions; I felt more comfortable with this.

Setting up a theme was a little more in depth. Again a separate file is required for the Treat styles:

and then that is passed to a theme provider as with other packages.

I had a few issues with styles picking up the theme as it didn’t seem to pick up by default like the other libraries when hot-loading. This required a restart of Gatsby to work which took a while to figure out.

Then in the component they are converted using hooks:

JSS (157.11kb)

JSS is an authoring tool for CSS which allows you to use JavaScript to describe styles in a declarative, conflict-free and reusable way. It can compile in the browser, server-side or at build time in Node.

Setup

A few packages are required to get up and running with JSS. The core package and default preset are required whilst the react-jss package is required for theming.

Documentation

The getting started is a one-pager and then everything else falls under advanced or packages. I couldn’t find documentation on theming without a Google search which directed me to the react-jss page under packaging. Most of the documentation focuses on the packages side.

Style

Like Treat, JSS uses camelCase to write elements. In the examples the styles are written as one large JavaScript object, which seemed overkill.

Within the React component, styles are converted using a hook and are added as a className (similar to Treat).

There are a lot of similarities between JSS and Treat, but I found JSS less intuitive especially around the large object for styles.

Conclusion

I see these four libraries split by two methods of interaction. Emotion and styled-components both stick to the classic CSS type styling. Whereas Treat and JSS appear more JavaScript based.

JSS’s setup and bundle size, especially as I only used one plugin, would rule itself out for me. At 157kb it’s over 170% of its nearest library and nearly 6000% bigger than the smallest. Adding more plugins will only add to this bundle size.

  • Emotion: 64.82kb
  • Styled-components: 92.47kb
  • Treat: 2.67kb
  • JSS: 157.11kb

Emotion and styled-components are very similar, however, I feel there is a little more to styled-components despite its larger bundle size. Both made me feel uneasy adding HTML elements in CSS via styled.div for example, but I guess that would become second nature in time. I'd also imagine running into some naming convention issues. For example, if I had a component named Button, I would have to add a new name for the CSS such as ButtonWrapper to avoid conflicts.

Treat seemed to be the most extendable, especially seeing what Seek have done with their braid-design-system. Its bundle size is also a big bonus, but it does come with a larger learning curve and more risk as it's not as well supported (less of a community behind it.)

Coming into this I fully expected styled-components to be my preferred choice and it is definitely the most popular. This was purely down to the fact that I’d heard so much more about this package than the others. But given this brief investigation I’d have to say that there are some very good challengers out there.

So if I was to pick, I would be between styled-components and Treat. If I wanted a quick site with minimum effort, then styled-components would be my choice. If I wanted to create a more complex and maintainable app, then I would take a gamble on Treat.

--

--