Image for post
Image for post

(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

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

Namespacing and avoiding specificity conflicts

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

Dead code elimination

Composition

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 Modules makes it possible to compose class names from other CSS modules:

Conditional styling

CSS-in-JS:

CSS Modules:

Call site customization

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

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

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

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:

Image for post
Image for post

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

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

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

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

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

Getting started

Using CSS-in-JS

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

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?

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!

P.S. Are you interested in crypto? I’ve launched a web app that helps you track your crypto-asset investments, and goes beyond just speculation by focusing on long-term financial analysis and knowledge building. If you’ve bought some Bitcoin or anything else, give it a try, I’m sure you’ll like it! https://unspent.io
— Philippe

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!

Written by

Founder unspent.io, prev @Buffer

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