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
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.
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.
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.
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.
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.