This is the first installation in a 2-part mini-series. Here we talk about the basics of styling in React, and in the second we talk about styling React components with CSS-in-JS.

If you’ve written a headless WebApp to render on a server, you’ll know one of the bigger challenges is figuring out the best way to style the rendered markup. Out of the box React provides some basic tooling for applying inline styles via style props, and the documentation goes over some general guidelines for using a global stylesheet…but for any sufficiently sophisticated WebApp, those solutions probably won’t scale well.

Styling in React is the Wild West with a myriad of approaches, all of them with subtle but important pros and cons. There are actually so many approaches we felt it couldn’t fit in one article, so we broke the subject out into two.

Andrew Hayakawa and Josh Compton both have spent a bunch of time over the last year exploring the various options for styling React projects with SSR in mind, and have condensed their findings into this mini-series. We’ll go over a few of the popular methods for styling in React, and outline their up- and down-sides—we hope it helps you decide which strategy works best for you!

Inline Styling

We will start off with Facebook’s out of the box approach—inline styling.

The style attribute accepts a JavaScript object with camel cased properties rather than a CSS string. This is consistent with the DOM style JavaScript property, and is more efficient

Inline styling is quick and easy to set up, uses methods native to React, is well-documented, and React’s dev team supports this approach so any issues that get reported are usually resolved pretty quickly. Taking the approach of inlining CSS via React also cuts down on any style-related dependencies—a benefit which the other approaches outlined in this article can’t claim.

However, inline styling can get really messy real quick (and bloats the final markup), and is difficult to simulate when debugging in browser tools. Not to mention inlined CSS can cause unexpected results that are hard to debug when mixed with global styles. Inlining all of the styles in your app also kind of defeats one of CSS’s biggest strengths in that you’re not leveraging the power of the Cascade.

While it’s a fairly basic example, it’s fairly illustrative of what this approach looks like from a developer experience:

import React, { Component } from 'react'
const CustomButton = ((props) => {
  const buttonStyles = {
    backgroundColor: 'red',
    color: 'white'
  };
  if (props.disabled) {
    buttonStyles.opacity = '50%';
  }
  return <button style={buttonStyles}>Click me!</button>
});
const randomComponent = ((props) => {
  return [
    <CustomButton />,
    <CustomButton disabled />
  ];
}

The result of randomComponent when rendered is:

<button style='background-color:red;color:white;'>Click me!</button>
<button style='background-color:red;color:white;opacity: 50%;'>Click me!</button>

A few benefits that you can see from this example:

It’s fairly simple and quick to get a prototype strung up with little extra setup

  • There aren’t external files or dependencies to deal with (which reduces your tooling dependencies for rendering outside of a browser environment)
  • The styles can be very tightly coupled to an individual component (rather than implicitly coupled with global styles)
  • You can procedurally build or extend style objects since they’re just JavaScript hashmaps, which means state-driven styles are fairly simple to manage
  • Your styles aren’t polluting the global cascade

A few gotchas as well:

  • Bloats rendered markup with all of those style tags
  • Your component styles don’t contribute to the Cascade—so your styles are not DRY, and cascaded styles will almost always lose out to these inlined styles unless you get cheeky
  • Pseudo elements and selectors aren’t supported, so no fancy :hover or :first-child effects
  • Extremely difficult to debug or experiment in browser tools

TL;DR

We get it, you’re busy, here’s the summarized breakdown (in no particular order):

Pros Cons
  • Self Contained components
  • Styles are JavaScript objects and can be extended
  • Easier when dealing with headless SSR
  • Part of React and will always be supported by Facebook
  • Messy and doesn’t look in production
  • Enlarges the HTML code if there are a lot of styles
  • Hard to debug
  • Does not support pseudo selectors

style-loader in Webpack

An alternative to Facebook’s method is using a JavaScript bundler like Webpack to import a style file. Webpack has a concept called a loader, which is essentially a plugin that parses files and pre-processes files as they are loaded (e.g: import/require) into a bundle.

You can use a loader like style-loader to bundle style files. To use it, you import the style file into your React code, just like importing a JS file. This enables you to have a style file per React component, making the coupling between styles and component tighter.

This is great for those who have a preexisting style file that needs to be incorporated in a project. The only effort needed is to break up the style code into its various components. The end result is your styles are injected into the head of your page in a style tag (so the CSS code is still global to the page as long as the component is mounted).

Here is a basic set up of how style-loader is incorporated in Webpack. We are using a CSS file, so css-loader is also needed:

In the webpack.config.js:

module.exports = {
    // The standard entry point and output config
    entry: {...},
    output: {...},
    module: {
        rules: [
         ...
            // Extract CSS files
            {
                test: /\.css/,
                use: [
                  {loader: 'style-loader'},
                  {loader: 'css-loader'}
                ]
            }
        ]
    }
}

With a pre-existing CSS file, you can simply import it into the React component:

import './style.css';
const component = (() => {
   return <div className='a-div-class-name'>Hello, World!</div>;
});

In the code above, the stylesheet is imported into the React component as is and injected into the head when the component is mounted. This is actually how we manage styles for the blog:

<html>
  <head>
    <style type="text/CSS">.a-div-class-name {...}</style>
  </head>
  <body>
    <div class="a-div-class-name">Hello, World!</div>
  </body>
</html>

An optional extra step, style-loader can also create unique class names (called CSS Modules) in your components so you don’t have to worry about making sure selectors don’t conflict:

import styles from './style.css';
const component = (() => {
   return <div className={styles.aClass}>Hello, World!</div>;
});

Above, the style is imported as a JavaScript object with keys of classnames for your component mapped to hashed classnames that are unique across the rendered markup on the page, freeing you from the pain of scoped selectors and class naming conventions…the downside being that your classes won’t be semantic, which can have implications for SEO and accessibility:

<html>
  <head>
    <style type="text/CSS">.asf31faachkl {...}</style>
  </head>
  <body>
    <div class="asf31faachkl">Hello, World!</div>
  </body>
</html>

This is an easy way to get started on styling a component. The downside to this is that since Node does not natively understand CSS files if they are imported in your React code, style-loader is not SSR friendly for a headless use case _if the styles aren’t bundled_.

TL;DR

Pros Cons
  • Easy to implement if style file(s) already exist
  • Styles can be organized per component
  • Hot Module Replacement (HMR) supported out of the box
  • Loaders for style pre processors (Sass, Less, PostCSS) available
  • Not great for headless SSR
  • Unmanaged classnames can lead to conflicting styles
  • Managed classnames aren’t semantic, implications for SEO/accessibility
  • WebPack dependency

Intermission

Phew! Ok, we’re at the half-way point…thanks for making it this far!

When you’re ready, we’ve got a followup post where we talk about styling React components with CSS-in-JS.