How Should I Style My React Application?

September 14, 2020 (4y ago)

React attempts to be as unopinionated as possible. This is both it's greatest strength and weakness.

Over the years, I've tried many different ways to style my React apps. Each solution has its pros and cons. This article will share my journey using CSS with React and how it's evolved to its current state. Then, I'll discuss pros/cons of different approaches for styling.

Vanilla CSS

Like most, I started web development with vanilla CSS. All you need is a single HTML and CSS file. Pretty simple.

<h1 class="header">Welcome</h1>
.header {
  font-size: 32px;
}

As I built more large applications, I began to understand some of the drawbacks.

  • Reusability. It's easy to create append-only stylesheets that become very complex.
  • Global Namespace. Since CSS has a global namespace, you can unintentionally target too many elements.
  • Colocation. It's hard to modularize your CSS, which makes it difficult to delete code safely.

At this point, I was introduced to CSS pre-processors. These tools aim to introduce new funtionality and compile back to vanilla CSS.

Sass

Sass, one of the most popular preprocessors, allows you to write more reusable, maintainable CSS. Some of the most popular features are variables, mixins, and modules.

  • Variables. I want to define global values once and share them throughout my stylesheets.
  • Mixins. I want to reuse particular snippets of CSS.
  • Modules. I want to split up my CSS files for a more maintainable codebase.
$font-stack: Helvetica, sans-serif;
$primary-color: #333;

body {
  font: 100% $font-stack;
  color: $primary-color;
}
@use 'base';

.header {
  font-size: 32px;
}

Sass helped me write more modular, reusable CSS – at least, for a while. Over time, I ran into new issues.

  • Naming. As your CSS grows, it's easy to have class names get out of hand.
  • Browser support. Certain features aren't supported across all browsers and require vendor prefixes like -moz- (Firefox), -webkit- (Safari), and -ms- (Internet Explorer and Edge).

To solve naming, I started to adopt BEM. Block Element Modifier is a methodology that helps you to create reusable components and organize CSS in a modular way, which keeps specificity low.

<button class="button">Cancel</button>
<button class="button button--primary">Submit</button>
.button {
  color: black;
  background-color: gray;
}

.button--primary {
  color: white;
  background-color: green;
}

Naming conventions helped but didn't fix the root cause: naming is hard. For browser support, it's possible to set up your Sass toolchain with a tool like autoprefixer to handle vendor prefixes. However, around this time, I started using component-based frameworks like React. This led me to explore CSS-in-JS.

CSS-in-JS

I started using CSS-in-JS when building a component library. At first, CSS-in-JS solved all my problems.

  • Specificity is solved by auto-generated class names.
  • Colocation is solved by putting CSS directly with the component, making it easy to delete code.
  • Browser support is solved by having autoprefixer built-in.
  • Variables are solved by creating a global theme.

However, naming was still a huge pain.

As the component library grew, I began to explore Theme UI and styled-system. These were more structured approaches to scaling CSS-in-JS to help enforce a design system. Plus, they helped solve naming. Trying to scale styled-components alone in a large application made me get really creative with names. You can only have so many container, wrapper, layout things. For example:

const HomeContainerWrapper = styled.div`
   padding: 8px;
   font-weight: bold;
   color: white;
   background: blue;
`;

<HomeContainerWrapper>
  Hello
</HomeContainerWrapper>

// Forget about naming
<Box
  padding={3}
  fontWeight='bold'
  color='white'
  bg='blue'
>
  Hello
</Box>

Now, I know what you're thinking. Inline styles 🤮. I'll admit, I wasn't sold at first. But it grew on me – and that brings us to now.

Current State

I've recently been working with CSS Modules and Tailwind CSS. Both of these approaches to styling React applications have prompted me to step back and evaluate the ecosystem.

Below, I've summarized pros and cons of various approaches to styling your React application. I tried to make this comparison as unopinionated as possible. The choice that's best for you likely depends on:

  • Your experience (stick with what you know).
  • Your team.
  • The size of your application.
  • What you're trying to build.

Pros and Cons

Vanilla CSS

You might use vanilla CSS if...
You don't want to add a toolchain. Vanilla CSS works with every browser & language.
You might not use vanilla CSS if...
You're building a large web application.

CSS Modules

CSS Modules would be my choice for smaller teams that don't need to share components across applications.

Next.js has support for CSS Modules, which means you don't have to worry about setting up Webpack. It also sets up autoprefixer making CSS Modules an attractive alternative to CSS-in-JS in some cases.

You might use CSS modules if...
You want class names scoped to the corresponding component.
You want to use CSS variables to reuse values.
You need your application to work without JavaScript.
You want the lowest barrier to entry.
You want to use any language, not just JavaScript.
You might not use CSS modules if...
You don't want to configure a toolchain like Webpack.
You need to distribute a package on NPM.
You want nesting support by default (requires PostCSS).
You don't want to context switch between files.

CSS-in-JS

There's many different CSS-in-JS libraries. The two most popular are styled-components and Emotion.

There's also zero-runtime solutions like Linaria, where CSS is extracted to CSS files at build-time. To see comparisons between different libraries, see CSS-in-JS benchmarks.

You might use CSS-in-JS if...
You want to distribute as reusable components on NPM.
You want to take advantage of anything in the JavaScript ecosystem.
You want to colocate styles with components, making it easy to delete code.
You need to support multiple themes.
You might not use CSS-in-JS if...
You don't want to configure a toolchain like Webpack.
You are sick of figuring out names for things.
You believe it breaks separation of concerns.
You don't want to enforce all consumers must use the same CSS-in-JS solution.

Theme UI / Styled System

Both Theme UI and styled-system abide by the System UI theme specification. This specification intends to help make UI components, libraries, and tools as interoperable as possible.

The primary use case for these libraries is implenting a component library and design system. One of my favorite libraries is Chakra UI.

You might use Theme UI / styled-system if...
You're building a component library.
You need to enforce a design system.
You want to spend less time trying to name components.
You might not use Theme UI / styled-system if...
You dislike providing styles as props.
You believe it breaks separation of concerns.

Tailwind CSS

Tailwind CSS has been rapidly growing in popularity. Many people are frustrated with the bloat of CSS frameworks like Bootstrap and want an alternative. Tailwind provides an impressive developer experience with a refined API.

With the combination of Tailwind UI, you can easily build extensible, well-designed applications.

You might use Tailwind if...
You want to never leave your HTML (or JSX).
You need to enforce a design system.
You want to spend less time trying to name components.
You might not use Tailwind if...
You don't want (potentially) long class names.
You don't want to learn Tailwind syntax, which is slightly different than standard CSS.

Conclusion

I hope this article has helped demystify styling your React applications. Did I miss anything or see something you'd change? Leave a comment below or reach out on Twitter.

Further Reading