Mastering Atomic CSS-in-JS: A Guide to Efficient and Modular Styling
With recent implementations from major companies like Facebook and Twitter, it seems that atomic CSS-in-JS is emerging as a new trend.
In this post, we'll explore what atomic CSS is, how it connects with functional or utility-first CSS frameworks like TailwindCSS, and how prominent organizations are incorporating it into their modern React applications.
Since I’m not an expert in this area, don’t expect an exhaustive analysis of its advantages and disadvantages. My goal is simply to give you a basic understanding of the concept.
Note: Atomic CSS should not be confused with Atomic Design.
What is atomic CSS?
You might be familiar with different CSS methodologies such as BEM and OOCSS...
Nowadays, Tailwind CSS and its utility-first approach are quite popular. This concept is similar to Functional CSS and Tachyon.
With a stylesheet full of utility classes, you can achieve a lot.
Atomic CSS takes the utility-first CSS approach to the extreme: each CSS class contains just one, distinct rule. This concept was first introduced by Thierry Koblentz (Yahoo!) in his 2013 article, "Challenging CSS Best Practices."
/* Atomic CSS */ .bw-2x { border-width: 2px; } .bss { border-style: solid; } .sans { font-style: sans-serif; } .p-1x { padding: 10px; } /* Not atomic, because the class contains 2 rules */ .p-1x-sans { padding: 10px; font-style: sans-serif; }
With utility or atomic CSS, we accept that combining structure and presentation layers is acceptable. For example, if we need to change a button’s color, we update the HTML rather than the CSS. This close coupling is also recognized in modern CSS-in-JS React codebases, but the CSS community was quick to realize that the traditional "separation of concerns" didn't always fit well with web development.
Specificity issues are minimized as we use simple class selectors.
Styling through markup has several benefits:
- The stylesheet remains smaller as we add new features.
- We can move markup around, and the associated styles move with it.
- When removing features, the related styles are removed simultaneously.
However, this approach can make the HTML a bit more cluttered. This might be a concern for server-rendered web applications, but the high redundancy of class names compresses well with gzip, similar to how duplicated CSS rules in stylesheets were effectively compressed in the past.
Utility or atomic CSS isn't necessary for every situation, only for the most common styling patterns.
Once your utility or atomic CSS is set up, it tends to remain stable and doesn’t grow significantly. It can be cached more aggressively (for example, by appending it to a vendor.css file, expecting it to remain unchanged across app redeployments). It is also quite portable and can be used in other applications.
Limits of utility/atomic CSS
Utility or atomic CSS can be quite intriguing, but they come with their own set of challenges.
Typically, utility or atomic CSS is crafted manually, requiring careful development of naming conventions. Ensuring these conventions are user-friendly, consistent, and not prone to bloat can be challenging. Questions arise about whether multiple people can work on this CSS while maintaining consistency and if it is vulnerable to the "bus factor" (risk of losing knowledge if someone leaves).
Developing a well-structured utility or atomic stylesheet requires significant effort upfront before you can effectively iterate on features that utilize it.
If you’re using utility or atomic CSS created by someone else, you'll need to familiarize yourself with their specific class naming conventions, even if you’re well-versed in CSS. These conventions can be opinionated, and you might not always agree with them.
Additionally, there may be times when you need extra CSS that isn't covered by your utility or atomic CSS. There isn’t a one-size-fits-all solution for adding these one-off styles.
Tailwind to the rescue
The Tailwind approach is quite practical and addresses some of the issues associated with utility or atomic CSS.
Instead of providing a universal utility CSS file for all websites, Tailwind offers a shared scope and naming conventions. Through a configuration file, you can generate a custom utility CSS tailored to your needs.
Knowledge of Tailwind is transferable to other applications, even if they don’t use the exact same class names. This is reminiscent of React's "Learn once, write anywhere" philosophy.
Reports suggest that Tailwind classes cover between 90% and 95% of typical styling needs, indicating that its scope is broad enough to minimize the need for additional one-off styles.
At this point, you might wonder whether to stick with atomic CSS or switch to Tailwind. What advantages does enforcing the atomic CSS rule of "one rule, one class" offer? It might lead to bulkier HTML markup and less convenient naming conventions, especially since Tailwind already includes many atomic classes.
So, should we move away from atomic CSS in favor of Tailwind?
While Tailwind is an excellent solution, there are still some challenges to consider:
- Learning an opinionated naming convention.
- Managing the order of CSS rule insertion.
- Removing unused rules efficiently.
- Handling remaining one-off styles.
Handwritten atomic CSS might not always be as convenient as using Tailwind.
Comparison with CSS-in-JS
CSS-in-JS and utility/atomic CSS share a relationship in that both approaches promote styling directly from the markup. They aim to achieve some of the benefits of inline styles, such as the ability to confidently reposition elements.
Christopher Chedeau played a significant role in popularizing CSS-in-JS within the React ecosystem. In various talks, he discusses the issues associated with traditional CSS:
Utility and atomic CSS address some of these issues as well, but they don’t completely resolve all problems, particularly the non-deterministic nature of style resolution.
If utility and atomic CSS have similarities, why not combine them?
Enter atomic CSS-in-JS
Atomic CSS-in-JS can be considered as a form of "automatic atomic CSS":
- No need to establish a class name convention manually.
- Both common and unique styles are handled in the same manner.
- Ability to extract critical CSS for a page and perform code-splitting.
- Opportunity to address issues related to the order of CSS rule insertion within JavaScript.
Currently, not all CSS-in-JS libraries support atomic CSS. Support for atomic CSS can vary, as it may be an implementation detail that can change or be optional.
I will highlight two specific solutions that have recently led to large-scale atomic CSS-in-JS implementations, based on two talks:
- React-Native-Web at Twitter (more details in Nicolas Gallagher’s talk)
- Stylex at Facebook (more details in Frank Yan’s talk)
Other notable libraries include:
- Styletron
- Fela
- Style-Sheet
- cxs
- otion
- css-zero
- ui-box
- style9
- stitches
- catom
- compiled
If you want to be added to the list or have any questions, feel free to reach out.
React-Native-Web
React-Native-Web is a fascinating library that allows rendering of React-Native primitives on the web. This isn’t about cross-platform mobile/web development; check out the talks for more details.
For web developers, it's important to know that React-Native-Web functions as a standard CSS-in-JS library, but it includes a limited set of primitive React components. Essentially, you can think of `View` as being equivalent to a `div` in your mind, and you’ll be able to use it effectively.
Created by Nicolas Gallagher while working at Twitter Mobile, React-Native-Web was gradually introduced around 2017/2018. Since then, it has been adopted by various companies such as Major League Soccer, Flipkart, Uber, and The Times. The most notable deployment was the 2019 Twitter desktop app, developed by a team led by Paul Armstrong.
Stylex
Stylex is a new CSS-in-JS library developed at Facebook for their 2020 rewrite, and it is currently in beta. There are plans to open-source it eventually, potentially under a different name.
It’s interesting to note that Nicolas Gallagher, the creator of React-Native-Web, joined Facebook two years ago. It’s not surprising to see some of his concepts being incorporated into Facebook’s new library.
Unlike React-Native-Web, Stylex doesn’t appear to be focused on cross-platform development.
All the information I have comes from the talk, so we’ll need to wait for more details.
Scalability
As anticipated with atomic CSS, both Twitter and Facebook have experienced a significant reduction in their CSS size, following a logarithmic curve. However, there is an initial cost for simpler applications.
- Previously, their site had 413 KB of CSS just for the landing page.
- Now, their new site has only 74 KB of CSS for the entire site, including dark mode.
Source and output
The two libraries appear to have similar and fairly straightforward APIs, though it's difficult to make a definitive comparison since we don’t have much information about Stylex yet.
It’s important to note that React-Native-Web will support expanded CSS shorthand syntaxes, such as `margin: 0`.
Production inspection
Let’s examine what the markup looks like on Twitter:
Now, let’s look at the new Facebook:
Many people might be shocked by this, but it actually functions well and remains accessible.
While navigating styles in the Chrome inspector might be a bit more challenging, developer tools can assist with this.
CSS rules order
Unlike manually written utility or atomic CSS, JavaScript libraries can handle styling independently of the order in which CSS rules are inserted. In traditional CSS, if there are conflicting rules, the last rule added to the stylesheet takes precedence, not necessarily the last class in a class attribute. Specificity issues are managed by using only simple class-based selectors.
In practice, these libraries prevent classes with conflicting rules from being applied to the same element. They ensure that the most recently declared style in the markup takes precedence. Classes that would be overridden are filtered out and do not appear in the DOM.
const styles = pseudoLib.create({ red: {color: "red"}, blue: {color: "blue"}, }); // That div only will have a single atomic class (not 2!), for the blue colorAlways blue!// That div only will have a single atomic class (not 2!), for the red colorAlways red!
If a class contains multiple rules and only one is overridden, the CSS-in-JS library cannot filter out just the overridden class without also removing the non-overridden rules.
Similarly, if a class includes a shorthand rule like `margin: 0`, and the override is `marginTop: 10`, the issue persists. The shorthand syntax like `margin: 0` is broken down into four distinct classes, allowing the library to filter out only the overridden classes with greater precision, ensuring they do not appear in the DOM.
You still prefer Tailwind?
Once you’re familiar with all the Tailwind naming conventions, you can quickly design a UI. It might feel less efficient to return to manually writing each CSS rule as you do with CSS-in-JS.
However, there’s nothing stopping you from creating your own abstractions on top of an atomic CSS-in-JS framework. For example, Styled-system may work with some CSS-in-JS libraries that support atomic CSS. You can even reuse Tailwind’s naming conventions in JS if that approach enhances your productivity.
Let’s look at some Tailwind code:
Now, let’s examine a random solution I discovered on Google, such as react-native-web-tailwindcss:
import {t} from 'react-native-tailwindcss';
In terms of productivity, this approach is quite similar. Additionally, using TypeScript can help prevent typographical errors.
Conclusion
That’s about all I have to share on atomic CSS-in-JS.
I haven’t used atomic CSS, atomic CSS-in-JS, or Tailwind in any large-scale production environments, so I may be mistaken in some areas. Feel free to reach out on Twitter if you have corrections or additional insights.
I believe atomic CSS-in-JS is a trend worth watching in the React ecosystem, and I hope you’ve found this post informative.
Since I couldn’t find any articles specifically about atomic CSS-in-JS, I wrote this primarily for my own reference. I wanted a resource to link to when discussing atomic CSS-in-JS in future blog posts. I plan to write more about React-Native-Web and cross-platform development, so stay tuned for more articles.