CSS Modules vs CSS-in-JS. Who wins?
Sergey
Posted on March 11, 2021
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!
Posted on March 11, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
December 13, 2021