Modular CSS with React

(Translations: Japanese)

At Buffer, we love React and are incrementally moving a lot of our front-end codebase to it. With Flux on top of it, we just think it’s a very sane way to build a complex product made of smaller, modular apps. As such, we see each new small app and feature as a new React brick to add to that edifice.

I’ve been working on one of those new features lately, and have further fallen in love with how easy it is to build and reason about a React+Flux application. React makes it smooth to build the UI declaratively from meaningful components, and Flux brings a sensible data flow to the mix.

A lot of thought was given to the challenges that arise from building complex applications, and this stack solves a lot of them beautifully.

Yet, I still wasn’t sure how to make styling live up to React’s modularity and reusability.

Fortunately, there have been some very interesting developments in CSS-land recently, with a lot of patterns and tools emerging to make modular CSS a reality.

CSS-in-JS

Vjeux shared a lot of great ideas around leveraging JS to solve many challenges with CSS in React: CSS-in-JS. The rationale is that a lot of things that are naturally hard to achieve with CSS become significantly easier, just because they’re easier to achieve with JS.

The general idea is to write CSS in the form of JS object literals, which feel close enough to the natural syntax, and apply them to React components using the styles attribute. Styles can live in separate, modular files that get imported into JS modules using regular import statements.

CSS Modules

A CSS Module is a CSS file in which all class names and animation names are scoped locally by default.

That’s the very simple premise of CSS Modules: each React component gets its own CSS file, which is scoped to that file and component. The magic happens at build time, when local class names – which can be super simple without risking collisions – are mapped to automatically-generated ones and exported as a JS object literal to use within React:

CSS Modules is deliberately very simple: it only adds a super small amount of new syntax to CSS to solve the CSS modularity challenge. Everything else remains the same.


Solving the modularity challenge

A good number of challenges come from having CSS rules live in the global scope. Let’s see how CSS-in-JS and CSS Modules solve them.

Namespacing and avoiding specificity conflicts

CSS-in-JS and CSS Modules are scoped locally by default, which means namespacing is unnecessary. And it also makes it possible to use very short and meaningful names for style rules, which naming conventions like BEM can’t afford. As a result, there’s no risk of naming collisions or specificity issues.

To define styles, CSS-in-JS will use object properties: { image: {} }
And CSS Modules will use simple class names: .image {}

Dead code elimination

With complex CSS selectors, it’s often hard to know if and where some styles are used. With modules and explicit dependencies, it’s much easier to know what’s used or not, and to be fully confident when removing unused styles. And since we’re talking about local variables for both CSS-in-JS and CSS Modules, that means build tools can also spot any unused local variables and remove them.

Composition

Both approaches offer composition as a solution for styling reuse.

To that end, CSS-in-JS can take advantage of anything JS has to offer, such as Object.assign().

CSS Modules allows to compose selectors with the composes rule:

Dependencies

CSS-in-JS is plain JS, so it can work with any module system to export/import styles.

CSS Modules makes it possible to compose class names from other CSS modules:

Conditional styling

Changing styles based on state and other conditions is another good use-case for composition.

CSS-in-JS:

CSS Modules:

Call site customization

A component’s style should be customizable by the one calling it. With reusable components, it’s felt liberating to have the component-specific styles such as its dimensions and default colors scoped to the component itself, and then have each parent handle the styles for positioning it and possibly tweak its look and feel a little.

With CSS-in-JS, passing styles from the parent to the child is a simple matter of merging style objects together:

And with CSS Modules, the same can be achieved by concatenating their class names together:

Cool things JS makes a bit easier than CSS

Both CSS-in-JS and CSS Modules bring great solutions for making styling modular, and that’s really awesome. In addition to modularity, all sorts of other feats can be desirable with styling, which JavaScript – by the nature of it – makes so much easier than vanilla CSS.

Still, those additional things remain achievable with CSS, by either relying on CSS pre-processors such as LESS and SASS, or taking advantage of future CSS features using new syntax ahead of time (hence pre-processing this syntax to make it run in today’s browsers).

Let’s see how CSS-in-JS and CSS Modules compare with those things which, today, feel easier to achieve with JS. For CSS Modules, we’ll be using upcoming CSS features rather than LESS/SASS, but those can also be an option.

Sharing style constants

Style constants are a nice tool for sharing common properties such as colors and sizes across components – or within the same one.

In the case of CSS-in-JS, since everything is JS, this is as easy as using variables, and exporting them to make them available to other components!

When it comes to CSS Modules, sharing constants inside a same stylesheet is also easy with CSS Custom Properties:

However, sharing those constants across stylesheets is a bit less convenient, as CSS Modules are meant to compose styles together (and make that super easy by importing styles from other files), and expect style composition to be used over constant sharing.

The intended CSS Modules way would look like so:

But if sharing constants is your thing, we’ll have to use what CSS Modules is built on: ICSS. It’s a small spec on top of standard CSS that adds :import and :export pseudo-selectors to enable CSS Modules. Using those, we’ll be able to export and import key/value pairs, that can be mapped locally to CSS custom props:

This is quite verbose, but each block has a desirable purpose: the first one explicitly imports a value from an external dependency, the second defines a local custom property that takes this imported value, and then that custom prop can be used as we see fit.

If you’d prefer writing less, at the cost of introducing globals – which we’re actively trying to get rid of, but I can think of pros and cons for sharing constants – there’s also a way! Using global custom props, the above code can be trimmed down to:

Sharing variables between JS and CSS

Even though sharing JS variables with CSS doesn’t feel ideal, many situations call for it.

With CSS-in-JS, JavaScript makes it once again super easy!

As for CSS Modules, well, it’s unfortunately a bit limited as of today.

The attr() CSS function can be used to retrieve the value of a given DOM attribute and use it with CSS, but browsers currently only support this function being used in the content property of pseudo-elements, and with string values. In time, and coupled with other CSS functions like calc(), any DOM attribute will be able to hold values that CSS can read and make calculations with – creating a powerful bridge between JS+HTML and CSS!

The dream:

What that dream would look like in a hypothetical future version of Chrome that’d support the definition of attr() in CSS3:

In the meantime, for any use-case that requires more than pseudo-elements and strings to share variables between JS and CSS, inline styles are the way to go.

Style computation and manipulation

When using CSS Modules, upcoming CSS specs introduce a lot of useful functions to manipulate property values, such as calc() and color().

And JavaScript’s expressiveness makes it an ideal candidate to manipulate styles in any imaginable way, colors included.

Common CSS features that JS struggles with

JS does make some styling pursuits easier than CSS itself.

But let’s not forget that CSS, as a language created to describe how documents should be presented, has been shaped to make styling those documents straightforward in many ways. JS doesn’t serve the same purpose, and as such, lacks some of the simplicity of CSS when it comes to some styling use-cases. What CSS does with pseudo-classes, pseudo-elements and media queries is harder to achieve with JS.

Pseudo-classes

In CSS, pseudo-classes such as :first-child and :nth-child allow styling elements based on their relationship with other elements in the document.

When styling using JS, those pseudo-classes aren’t around, but it’s possible to apply styling selectively to some elements within render loops. For example, the equivalent to :nth-child(even) would look like this using JS:

When the styling of an element in JS should be based on a simple condition such as the index in the loop, the method is slightly more verbose than CSS but still easy to take care of. Here’s a cool cheat sheet for some of those pseudo-classes equivalents in JS (from this great talk on inline styles):

Another thing pseudo-classes are good for, is changing styles based on an interaction with the page. This is where in CSS the ubiquitous :hover:focus and :active really shine!

In JS, event listeners such as onMouseEnter and onMouseLeave would have to be used to update the component’s state, and apply the “hover” style accordingly.

This is rather unwieldy (although arguably more powerful), and many libraries have abstracted that away; among them, I especially like Radium, which would make a hover style look like this:

Pseudo-elements

In CSS, ::after::before::placeholder often come in handy to style certain parts of a document, and the same could be said of ::first-letter::first-line, and ::selection.

When it comes to JS, those are hard to emulate easily. A case could certainly be made about using actual elements instead of ::after and ::before, but it’d be cumbersome to target ::first-letter or ::first-line, and impossible to target ::placeholder or ::selection without an abstraction that mirrors elements for styling purposes, which sounds a bit overzealous.

Media queries

To handle media queries with JS, relying onwindow.matchMedia() to apply styles conditionally is also less practical than its delightful CSS counterpart. Libraries like Radium can also help with that.


Getting started

Using CSS-in-JS

In the end, CSS-in-JS is nothing more than inline styles, which you can start using by leveraging React’s style attribute.

Using a library such as Radium will be helpful for several features that usually come with vanilla CSS: :hover:focus and :active pseudo-classes, and media queries. Radium will also take care of auto-prefixing, which is a godsend considering that styling with JS also means doing away with the usual CSS tooling such as Autoprefixer!

Using CSS Modules

For a React component that you’d like to style, simply create a CSS file that’ll contain the styles for that component. Only simple class names should be used (e.g. .image), since all the styles in that file are local, and should only be useful in the context of that component.

Then, import this CSS file into the React component just like you’d import anything else, and apply the imported class names using the className attribute:

Webpack can be used with its css-loader and the modules option to build the app. The output will look like:

In this example Webpack config, ExtractTextPlugin is used to create a CSS bundle alongside the JS bundle, and the localIdentName css-loader option helps create convenient class names to help locate styles during development (a shorter hash can be used for production code).

CSS modules can also be used:

  • With Browserify, using the css-modulesify plugin (it’s still experimental, I did run into some issues with it in the past)
  • With static sites, and on the server side, with the PostCSS plugin postcss-modules
  • Without React!

Which one to pick?

Both those approaches seem to offer similar benefits: modularity, composability, the liberating experience of local scopes… While they share advantages, their implementation differs: one is actual CSS living in CSS files and is wired with JS modules using imports and hashed class names at build time, the other is actual JS from the start (although it almost looks like CSS), and is composed and attached to elements in the form of inline styles at execution time. They also both make some things easier than the other, though everything remains possible with either!

As is often true, it’s a matter of use-case, and taste! CSS Modules add another build step, but as Pete Hunt eloquently says about CSS-in-JS, hinting at some tooling and performance lag, “there hasn’t been a lot of sweat put into ‘inline styles for everything’ by browser vendors yet”.

This time I went with CSS Modules: CSS feels like the right tool for the job, and I’ve been really happy with that combination of CSS Modules and pre-processing[1][2] CSS’s powerful upcoming features. It’s been good for maintainability, clarity, reliability – and beauty, there has to be some beauty in code.

But CSS-in-JS looks like a great alternative, and I’d love to hear about the experience of anyone using it – and what you think of this next-level CSS-in-JS strategy!


Footnotes:

1. CSS pre-processing comes with limitations: it’ll only transform what can be determined or calculated ahead of time (i.e. what isn’t dependent on the DOM or on an element’s layout or position) – pre-processing only unlocks part of the power those upcoming CSS features will have at runtime!
2. And I’ll be absolutely ecstatic when
Houdini makes complete CSS polyfills possible, taking things so much further than pre-processing! Yay for Houdini!