Introduction
In modern React application development, there are many approaches to organizing application styles. One of the popular ways of such an organization is the CSS-in-JS approach (in the article we will use styled-components as the most popular solution) and CSS Modules. In this article, we will try to answer the question: which is better CSS-in-JS or CSS Modules?
So let's get back to basics. When a web page was primarily set for storing textual documentation and didn't include user interactions, properties were introduced to style the content. Over time, the web became more and more popular, sites got bigger, and it became necessary to reuse styles. For these purposes, CSS was invented. Cascading Style Sheets. Cascading plays a very important role in this name. We write styles that lay like a waterfall over the hollows of our document, filling it with colors and highlighting important elements.
Time passed, the web became more and more complex, and we are facing the fact that the styles cascade turned into a problem for us. Distributed teams, working on their parts of the system, combining them into reusable modules, assemble an application from pieces, like Dr. Frankenstein, stitching styles into one large canvas, can get the sudden result... Due to the cascade, the styles of module 1 can affect the display of module 3, and module 4 can make changes to the global styles and change the entire display of the application in general.
Developers have started to think of solving this problem. Style naming conventions were created to avoid overlaps, such as Yandex's BEM or Atomic CSS. The idea is clear, we operate with names in order to get predictability, but at the same time to prevent repetitions.
These approaches were crashed of the rocks of the human factor. Anyway, we have no guarantee that the developer from team A won't use the name from team C.
The naming problem can only be solved by assigning a random name to the CSS class. Thus, we get a completely independent CSS set of styles that will be applied to a specific HTML block and we understand for sure that the rest of the system won't be affected in any way.
And then 2 approaches came onto the stage to organize our CSS: CSS Modules and CSS-in-JS. Under the hood, having a different technical implementation, and in fact solving the problem of atomicity, reusability, and avoiding side effects when writing CSS.
Technically, CSS Modules transforms style names using a hash-based on the filename, path, style name. Styled-components handles styles in JS runtime, adding them as they go to the head HTML section (<head>).
Approaches overview
Let's see which approach is more optimal for writing a modern web application!
Let's imagine we have a basic React application:
import React, { Component } from 'react';
import './App.css';
class App extends Component {
render() {
return (
<div className="title">
React application title
</div>
);
}
}
CSS styles of this application:
.title {
padding: 20px;
background-color: #222;
text-align: center;
color: white;
font-size: 1.5em;
}
The dependencies are React 16.14, react-dom 16.14
Let's try to build this application using webpack using all production optimizations.
we've got
uglified JS - 129kb
separated and minified CSS - 133 bytes
The same code in CSS Modules will look like this:
import React, { Component } from 'react';
import styles from './App.module.css';
class App extends Component {
render() {
return (
<div className={styles.title}>
React application title
</div>
);
}
}
uglified JS - 129kb
separated and minified CSS - 151 bytes
The CSS Modules version will take up a couple of bytes more due to the impossibility of compressing the long generated CSS names.
Finally, let's rewrite the same code under styled-components:
import React, { Component } from 'react';
import styles from 'styled-components';
const Title = styles.h1`
padding: 20px;
background-color: #222;
text-align: center;
color: white;
font-size: 1.5em;
`;
class App extends Component {
render() {
return (
<Title>
React application title
</Title>
);
}
}
uglified JS - 163kb
CSS file is missing
The more than 30kb difference between CSS Modules and CSS-in-JS (styled-components) is due to styled-components adding extra code to add styles to the <head> part of the HTML document.
In this synthetic test, the CSS Modules approach wins, since the build system doesn't add something extra to implement it, except for the changed class name. Styled-components due to technical implementation, adds dependency as well as code for runtime handling and styling of <head>.
Now let's take a quick look at the pros and cons of CSS-in-JS / CSS Modules.
Pros and cons
CSS-in-JS
cons
- The browser won't start interpreting the styles until styled-components has parsed them and added them to the DOM, which slows down rendering.
- The absence of CSS files means that you cannot cache separate CSS.
- One of the key downsides is that most libraries don't support this approach and we still can't get rid of CSS. All native JS and jQuery plugins are written without using this approach. Not all React solutions use it.
- Styles integration problems. When a markup developer prepares a layout for a JS developer, we may forget to transfer something; there will also be difficulty in synchronizing a new version of layout and JS code.
- We can't use CSS utilities: SCSS, Less, Postcss, stylelint, etc.
pros
- Styles can use JS logic. This reminds me of Expression in IE6, when we could wrap some logic in our styles (Hello, CSS Expressions :) ).
const Title = styles.h1`
padding: 20px;
background-color: #222;
text-align: center;
color: white;
font-size: 1.5em;
${props => props.secondary && css`
background-color: #fff;
color: #000;
padding: 10px;
font-size: 1em;
`}
`;
- When developing small modules, it simplifies the connection to the project, since you only need to connect the one independent JS file.
- It is semantically nicer to use <Title> in a React component than <h1 className={style.title}>.
CSS Modules
cons
- To describe global styles, you must use a syntax that does not belong to the CSS specification.
:global(.myclass) {
text-decoration: underline;
}
- Integrating into a project, you need to include styles.
- Working with typescript, you need to automatically or manually generate interfaces. For these purposes, I use webpack loader:
@teamsupercell/typings-for-css-modules-loader
pros
- We work with regular CSS, it makes it possible to use SCSS, Less, Postcss, stylelint, and more. Also, you don't waste time on adapting the CSS to JS.
- No integration of styles into the code, clean code as result.
- Almost 100% standardized except for global styles.
Conclusion
So the fundamental problem with the CSS-in-JS approach is that it's not CSS! This kind of code is harder to maintain if you have a defined person in your team working on markup. Such code will be slower, due to the fact that the CSS rendered into the file is processed in parallel, and the CSS-in-JS cannot be rendered into a separate CSS file. And the last fundamental flaw is the inability to use ready-made approaches and utilities, such as SCSS, Less and Stylelint, and so on.
On the other hand, the CSS-in-JS approach can be a good solution for the Frontend team who deals with both markup and JS, and develops all components from scratch. Also, CSS-in-JS will be useful for modules that integrate into other applications.
In my personal opinion, the issue of CSS cascading is overrated. If we are developing a small application or site, with one team, then we are unlikely to encounter a name collision or the difficulty of reusing components. If you faced with this problem, I recommend considering CSS Modules, as, in my opinion, this is a more optimal solution for the above factors. In any case, whatever you choose, write meaningful code and don't get fooled by the hype. Hype will pass, and we all have to live with it. Have great and interesting projects, dear readers!
Top comments (23)
One pro of CSS, the hot reload is instant when you just change CSS, with CSS in JS the project is recompiled. For CSS-in-JS I find easier to reuse that code in a React Native project.
My personal conclusion is that we are constantly trying to avoid CSS but at the end of the day, CSS will stay here forever.
Great article btw!
I ran into issues with css modules that styled components seemed to solve. But i ran into issues with styled components that I wouldn't have had with plain scss.
So some things to think about:
Styled components is a lot more overhead because all the styled components need to be complied into stylesheets and mounted to the head by javascript which is a blocking language.
On SSR styled components get compiled into a ServerStyleSheet that then hydrate the react dom tree in the browser via the context api. So even then the mounting of styles only happens in the browser but the parsing of styles happens on the server - that is still a performance penalty and will slow down the page load.
In some cases I had no issues with styled components but as my site grew and in complex cases I couldn't help but feel like it was slower, or didn't load as smoothly... and in a world where every second matters, this was a problem for me.
Here is an article doing benchmarks on CSS vs CSS in JS:
pustelto.com/blog/css-vs-css-in-js...
I use nextjs, it is a pity they do not support component level css and we are forced to use css modules or styled components... where as with Nuxt component level scss is part of the package and you have the option on how you want the sites css to bundled - all in one file, split into their own files and some other nifty options. I hope nextjs sharped up on this.
A big tip that might help.
Why not use SCSS and unique classNames: For example create a unique container className (name of the component) and nest all the other classNames under that unique container className.
I agreed, CSS Modules make a lot more sense to me over Styled Components, always have!
@Petar Kokev If something I learned from this years of working with React and other projects is that the correct library for project isn't the correct library for another. So the mos important think that we need to do is select the tools, libraries and technologies that fit better to the current project. In this case you can't use Styled-components on sites that require a good SEO, becouse the mos important think here is the SEO and you cant sacrify it.
How about having to deal with libraries like Material UI with next js?
I have an issue to decide whether to use just makeStyles function or should we use styled components?
My main concern is code longevity and maintenance without any issues
My big issues with styled components is they are deeply coupled with your code. I've opted to use emotion's
css
utility exclusively and instructed my team to avoid using any of the styled component features. We've loved it but this was a few years ago. For newer projects I'm going with the css modules design.Also why does anyone care about sass anymore? With css variables and the css nesting module in the specification, you get the best parts of sass with vanilla css. The other features are just overkill for a css-module that should represent a single react component and thus nothing
:global
. Complicated sass directives and stuff are just overkill. Turn it into a react component and don't make any crazy css systems.Same I was trying to revamp my personal site, I discovered that I would have to rewrite alot of things, and then I later gave up. I would advice css modules are the way to go, and it greatly helps with SEO.
And in teams using SC, naming becomes an issue because some people don't know how to name components and you have to scroll around, just to check if a component is a
h1
tag 🤮CACHEing I can't stress this enough, for enterprise in-house apps it doesn't really matter, but for everyday consumer-essentric apps CACHEing should not be overlooked
You can still have a top-level css file that isn't a css module for global stuff
How about css-in-js frameworks like material-ua, chakra-ui and others? In my opinion, they dramatically speed up development.
It is not true that with styled-components one can't use scss syntax, etc. styled-components supports it.
Good post. I've been using CSS modules for a short time now and I like it. Allows everything to be nicely compartmentalized.
I also like that it gives more freedom to name classes in smaller chunks of CSS code.
Instead of using it like so:
{styles.my_class}
I preffer{s.my_class}
makes the code looks nicer and more concise.In my personal opinion I see Styled Components more for a Single Page Aplications where the SEO isn't important and is unecessary to cache css files. In the case of static web site or a site that must have a good SEO the Module-Css is better.
@greggcbs My recomendation is to use code splitting if you have problem with the performans when you use Styled-Components in your project, in order to avoid brign all code in the first load of the site.
Good article @sergey
This is awesome!
I'm quite new to Web dev in particular and when starting a new project, I've often wondered which approach is better as I could see pros and cons to both, but I never found the time to dig in.
Thanks for pulling all this together into a concise blog post!
I'm sorry but it seems that you don't have much experience with Styled Components.
"And the last fundamental flaw is the inability to use ready-made approaches and utilities, such as SCSS, Less and Stylelint, and so on."
Not a single thing here is true. SCSS is the original syntax of the package, you can use Stylelint as well. There are a lot more "pros" which are not listed here.
By working with JS you are opened to another world.
I'll list some more "pros" from the top of my head:
In these days CSS has become a monster. You have inheritance, mixins, variables, IF statements, loops etc. Sure they can be useful somewhere but I'm pretty sure that most of you just need to center that div. So in my personal opinion we should strive to keep CSS as simpler as possible (as with everything actually) and I think that Styled Components are kind of pushing you to do exactly that. Don't re-use CSS, re-use components! The only global things you should have are probably just the color theme and animations.
With Vue, all you need to do is add
scoped
to the style attributeIf you're creating design system within your app, you can cache the js files by splitting your JS bundles.
Google Lighthouse is saddened by your CSS-in-JS 😀
Whilst such css-in-js frameworks like MaterialUI dramatically speed up development (that's true) they, from another side, beat the app perfomance because of too heavy. In that case CSS modules are more preferred.
🚀🚀
Both have pros and cons I alternate between the two.