Andrei Pfeiffer logo
Back to Articles

The evolution of scalable CSSPart 6: Atomic CSS

CSS
8 min read

The evolution of scalable CSS is a multi-part chronicle intended to document the progress of tools, practices and techniques that enable us to write maintainable CSS at scale.

  1. Introduction
  2. Part 1: CSS scalability issues
  3. Part 2: Good practices
  4. Part 3: CSS Processors
  5. Part 4: Methodologies and Semantics
  6. Part 5: Styles Encapsulation
  7. Part 6: Atomic CSS
  8. Part 7: CSS-in-JS
  9. Part 8: Type-safe CSS
  10. Epilogue

In the previous part of this series, Part 5: Styles Encapsulation, we've covered different methods to scope our styles to the component they belong to, preventing them from leaking into the global namespace. So far, we've only addressed Semantic CSS, but now it's time to branch off and explore alternative paradigms for authoring CSS styles.

This chapter examines Atomic CSS, a fundamentally different approach that employs non-semantic CSS classes usually containing a single CSS rule.


Here's what we'll cover next:


Timeline of scalable CSS evolution, highlighting the Atomic CSS branch (in green) and pinpoiting notable Atomic CSS frameworks: BASSCSS in 2013, ACSS and Tachyons in 2014 and Tailwind CSS in 2017
Atomic CSS branched off in 2013 as a completely different paradigm, currently being an alternative to the standard Semantic CSS approach

Overview

Fundamentally, the motivation for Atomic CSS is to "avoid the headache of writing and managing semantic stylesheets" due to global scope, complex selectors, specificity problems, and source code duplication. To solve these problems, Atomic CSS contradicts all Semantic CSS principles.

Atomic CSS consists of a collection of "atomic", non-semantic, single-purpose, utility CSS classes. They typically include a single CSS rule which fully conveys their implementation, not their meaning.

/* non-semantic CSS classes */
.text-red { color: #ff2222; }
.text-green { color: #11ee11; }

The above class names explicitly describe what they do: "display red/green text". Consequently, anyone on the team can easily reference them directly in their HTML markup without touching their CSS files.

Semantic vs. Non-semantic

It's worthnoting that there's a subtle, but significant difference between semantic and non-semantic names, even if they include the same implementation:

/* semantic class names, describing the content */
.text-error { color: #ff2222; }
.text-success { color: #11ee11; }

  • The names text-error and text-success don't explicitly describe their entire implementation.
  • Their styles will likely change in time due to new business requirements or design updates.
  • The names refer strictly to "error/success content". However, we might want to reuse the same styles for other purposes, like "positive/negative balance" or "increasing/decreasing trends", which have different semantics.

Semantic classes usually contain other style rules as well, like text formatting, alignment, spacing, etc. As a consequence, semantic classes have limited reusability.

Reusability

The main reason for using utility classes is high reusability, which provides consistency both in implementation and usage. However, semantics and reusability are slightly antagonistic.

/* 🎯 more specific, less reusable class */
.title {
  font-weight: bold;
  color: green;
}
  • We can only use the above title class when we want the text content to be both bold and green. For instance, to have a different title in bold and red, we would have to create a new class.
  • The more CSS rules we add to a CSS selector, the more specific it becomes, creating a tight coupling between the styles and their usage.

Highly specific and tightly coupled implementations are generally less reusable.


Since semantic classes specifically describe the content, their reusability is typically limited. Therefore, we would have to adopt a non-semantic mindset to benefit from high reusability.

/* ♻️ highly reusable individual classes */
.bold {
  font-weight: bold;
}
.text-green {
  color: green;
}

Splitting the implementation into two separate classes, we can use them individually or compose them with other classes. Single-rule CSS selectors have the highest degree of reusability.

Functional CSS

Atomic CSS is also known as Functional CSS as it borrows a few key principles from functional programming:

  • Composition because we can combine these CSS classes in any way we want. Think of them as lego blocks, which can be combined in countless ways.
  • Immutability as we'll never override, or mutate, defined CSS classes. A .bold class should always have the font-weight: bold; implementation.
  • Purity because the CSS classes should not depend on any external factors and should always render the same result, no matter where we apply them.
NoteSome exceptions to purity include theming and media queries, where certain classes should behave differently depending on the context.

Utility CSS classes

The benefits of Atomic CSS are obvious when we think of utility or helper CSS classes. For example, even with Semantic CSS approaches, we'll often use various reusable classes, including:


However, completely switching to an Atomic mindset requires additional work. Using Atomic CSS is fundamentally different than Semantic CSS.

To begin with, we don't write CSS when new styling is required. Instead, we define the required CSS classes upfront, taking all the required combinations into account. So, for instance, considering text alignment options, we'll define several CSS classes:

/* atomic CSS classes for text alignment */
.text-left { text-align: left; }
.text-center { text-align: center; }
.text-right { text-align: right; }
.text-justify { text-align: justify; }

Since all the required CSS code is defined beforehand, we won't be adding new CSS code in the long term. Instead, we will only reuse existing CSS classes. Thinking about it, most maintenance and scalability CSS issues simply disappear.


However, defining all the atomic CSS classes required to build real applications is not trivial. There are many categories of classes that we'll have to consider:

  • built-in classes for CSS rules that have predefined values, such as text-decoration, list-style-type, font-weight, display, etc;
  • custom classes for CSS rules that don't include predefined values, such as font-size, color, background-color, padding, margin, etc;
  • pseudo classes for handling interactive states like hover, focus, active or input states such as checked or disabled;
  • media query variants for managing responsive styles.

This is where Atomic CSS frameworks enter the scene because they provide the complete set of CSS classes required to build complex applications.

Frameworks

Even though there is a multitude of frameworks that implement various approaches to Atomic CSS, only a handful of them have become popular. Since there are some subtle and interesting differences between them, let's look briefly at their particularities.

BASSCSS

According to Github Insights, the first Atomic CSS framework, released in 2013, is BASSCSS. It contains utilities for layout, spacing, and typography, making it more suited for design and rapid prototyping. In addition, colors and element styling are available as add-ons.

<p class="block center bold black"></p>

One thing to notice is that the class names are readable and self-explanatory.

Tachyons

The following year, in 2014, Tachyons was released. It introduced interactive pseudo-classes for :active, :hover and :focused states.

<p class="db tc b black o-50 glow:hover"></p>

The syntax is less intuitive and more cryptic. But, at the same time, it's more succinct, rendering smaller HTML markup.

ACSS

ACSS or Atomized CSS was also released in 2014. However, instead of using a predefined set of classes like the other frameworks, it included a build step to generate only the CSS classes that are actually used.

<p class="D(b) Ta(c) Fw(b) C(black) Op(0.5) Op(1):h"></p>

The syntax, on the other hand, is even less intuitive than the previous frameworks.

Tailwind

In 2017, Tailwind CSS was released, supporting even pseudo-elements which are generally tricky to implement with Atomic CSS approaches. Tailwind also gained a lot of popularity in a relatively short amount of time, according to State of CSS and npm trends.

<p class="block text-center font-bold text-black opacity-50 hover:opacity-100"></p>

It's worth noticing the return to more verbose and self-explanatory class names.

Criticism

Atomic CSS is a pretty dogmatic approach. As a consequence, it got a lot of critiques, although some of them are highly opinionated or not 100% accurate. However, there are several recurring limitations and drawbacks that should be mentioned:

  1. Certain pseudo-elements cannot be easily implemented using Atomic CSS principles, like ::before and ::after, since classes must be explicitly applied to existing elements.

  2. Descendant and child combinators are not supported. In large applications, it's generally discouraged to use such selectors, but they might be required in specific scenarios.

  3. Debugging CSS code in browsers' devtools is cumbersome, as we have only one style rule per CSS class.

  4. We also have to learn a new language considering the entire set of predefined classes, regardless of which Atomic CSS framework we choose.

  5. And last but not least, some argue that Atomic CSS bloats the HTML markup during Server-Side Rendering (SSR) because we're simply moving code from CSS to HTML.

    <!-- Example of Twitter HTML code using Atomic CSS -->
    <img class="css-18t94o4 css-1dbjc4n r-1niwhzg r-sdzlij r-1phboty r-4iw3lz r-1xk2f4g r-109y4c4 r-1ii58gl r-25kp3t r-1ny4l3l r-1udh08x r-wwvuq4 r-u8s1d r-o7ynqc r-6416eg r-lrvibr r-92ng3h" />
    

Even though some of the drawbacks mentioned above are subjective or even debatable, one thing is certain: we cannot use Atomic CSS exclusively as it doesn't support the entire CSS syntax.

Symbiosis

Atomic CSS frameworks are also called utility-first, not utility-only frameworks. If we analyze this alternative definition, it doesn't convey that we "shouldn't write a single line of CSS code ever again".

Just as most Semantic CSS frameworks also include single-purpose utilities for colors, display properties, positioning, and more, we could also write our own semantic CSS custom classes to cover the limitations of Atomic CSS frameworks.


The semantic and the atomic (non-semantic) approaches are quite the opposite at the fundamental level. However, we could adopt a pragmatic and eclectic mindset, instead of being dogmatic and following a single school of thought.

In the end, all these frameworks are just tools. They're built by developers to help other developers write maintainable CSS code.


Objectively speaking, Atomic CSS solves most of the problems of Semantic CSS, namely code duplication, specificity, increasing code output, or complex selectors, just to name a few. Therefore, Atomic CSS proves to be a valid alternative to Semantic CSS.


In the following chapter, Part 7: CSS-in-JS, we'll explore a novel approach to CSS by moving style definitions to JavaScript files. As a result, we benefit from explicit dependencies and trivial variable sharing. In addition, CSS-in-JS provides all the features of styles encapsulation, CSS Processors, Atomic CSS, and so much more.


References and further reading

Scroll to top