CSS Modules vs CSS-in-JS vs Atomic CSS: Which One to Choose for Your React.js Project?
Currently, there are three popular approaches in React: CSS Modules, CSS-in-JS, and Atomic CSS. In many cases it’s not possible to give you a straight answer as to what’s the best approach, but I want to give you another perspective that can help you in this decision-making process.
CSS Modules and Sass
One of the common “issues” with CSS in React projects is that importing a css code in a component is loaded into a global scope, which can result in conflicts. CSS Modules is a build step of a module bundler that scopes class names into a namespace.
I put CSS Modules and Sass with BEM into the same category, because both of them try to solve the same issue with CSS global namespacing. I believe this approach is still valid, but it can easily fail if React developers are not experienced in CSS development. It seems easy to maintain CSS in simple projects, but even engineers at big tech companies such as Facebook were struggling to maintain their CSS code base.
CSS-in-JS
Later, there was a new approach CSS-in-JS that is still very popular nowadays. I was surprised by how easy it’s to make style code maintainable without any learning curve. Even back-end developers without any front-end experience can implement a maintainable layout. One of the main benefits of this approach is implementing dynamic styles through props and easy integration with Typescript:
import styled from '@emotion/styled/macro';
import { theme } from '../../../styles/theme';
const { sizes, colors } = theme;
type Side = "bottom" | "left" | "right" | "top";
interface ArrowProps {
arrowX: number;
arrowY: number;
staticSide: Side;
}
const gapFromAnchorElement = 4;
export const Arrow = styled.div<ArrowProps>`
position: absolute;
pointer-events: none;
left: ${({ arrowX }) => `${arrowX}px`};
top: ${({ arrowY }) => `${arrowY}px`};
${({ staticSide }) => ({
[staticSide]: `-${sizes.gap - gapFromAnchorElement - 2}px`,
...(staticSide === 'left' || staticSide === 'right'
? {
borderTop: `${sizes.gap - gapFromAnchorElement}px solid transparent`,
borderBottom: `${sizes.gap - gapFromAnchorElement}px solid transparent`,
}
: {}),
...(staticSide === 'left' ? { borderRight: `${sizes.gap - gapFromAnchorElement}px solid ${colors.White};` } : {}),
...(staticSide === 'right' ? { borderLeft: `${sizes.gap - gapFromAnchorElement}px solid ${colors.White};` } : {}),
})};
`;
In general, it’s a great choice, but there are 2 issues with this approach:
1. There is a lot of boilerplate code even in simple styles. For this reason productivity with this approach is decreasing significantly.
2. It’s not a good choice for SEO optimised React projects. Most CSS-in-JS libraries injects the generated stylesheet at the end of the head of the document during runtime. They are not able to extract styles into css files. It’s not possible to cache CSS. For this reason, CSS-in-JS is not a good approach for projects where you need to optimize First Contentful Paint and other SEO performance metrics. For example, CSS-in-JS is not a good fit for e-commerce Next.js projects.
On the other hand, CSS-in-JS is a great choice for projects that require deep Typescript integration and SEO performance metrics don’t matter. Enterprise or data oriented projects is a good fit, because they require Typescript integration and you don’t care about caching css and SEO performance metrics.
Atomic CSS
Then, we have a very popular approach that is called Atomic CSS. In this approach, you use utility classes to implement styles. It’s easy to keep your style code maintainable without any learning curve. The main advantage compared to CSS-in-JS is that it significantly increases productivity. There is just one disadvantage: the code is less readable.
import { ICard, SearchItemStatus } from '../SearchResults';
export function ResultCard({
title,
snippet,
url,
status,
isFocused,
}: ICard & {
isFocused: boolean;
}) {
const cardColor =
status === SearchItemStatus.ACCEPTED
? 'bg-green-100 border-green-300'
: status === SearchItemStatus.REFUSED
? 'bg-yellow-100 border-yellow-300'
: status === SearchItemStatus.BANNED
? 'bg-red-100 border-red-300'
: 'bg-white border-gray-200';
return (
<div
className={`flex justify-start items-center ${cardColor} rounded-md border py-7 px-7 ${
isFocused ? 'outline-blue' : ''
}`}
>
<div className="flex flex-col items-start">
<div className="flex items-center mb-1 text-sm text-left">
<a href={url} target="_blank" rel="noreferrer">
{url}
</a>
</div>
<div className="mb-1 text-lg text-blue-700">{title}</div>
<p className="text-sm text-left">{snippet}</p>
</div>
</div>
);
}
The most popular atomic CSS framework is Tailwind. It has its own preconfigured design system and theme configuration that you can customise based on your needs. Designers can use and customise the Tailwind Design Kit to build the design system. A well-defined design system with css framework and its css theme configuration is a big benefit of collaboration between developers and designers. If you use Tailwind, you need to use the Tailwind CSS IntelliSense plugin, because learning all utility classes wouldn’t make you more productive.
CSS Modules vs Atomic CSS
Currently, there are two camps:
CSS Module camp: These developers are experienced CSS developers. They are aware of the atomic CSS approach and its attempts before Tailwind. They don’t need it, because most of these devs started with CSS development before SPAs existed. They learned their lesson and know how to make CSS code maintainable and consistent.
Atomic CSS camp: These developers enjoy all benefits provided by Tailwind. Even developers from the CSS Module camp moved to this camp.
Conclusion
In this blog post, I wanted to highlight the pros and cons of using CSS modules vs CSS-in-JS vs atomic CSS in React projects. CSS-in-JS is the winner for specific types of projects. If you decide between CSS Modules and Atomic CSS, the decision is not so easy. You need to take into account how the team is experienced in CSS development and if you can benefit from atomic CSS framework such as Tailwind.
If you decide to go with CSS Modules with developers not so experienced in CSS, you can achieve great results. Stylelint can help you to set up specific rules for CSS development.