Learn about CSS Architecture: Atomic CSS
The following is an extract from our book, CSS Master, written by Tiffany Brown. Copies are sold in stores worldwide, or you can buy it in ebook form here.
If BEM is the industry darling, Atomic CSS is its rebellious maverick. Named and explained by Thierry Koblentz of Yahoo in his 2013 piece, Challenging CSS Best Practices, Atomic CSS uses a tight library of class names. These class names are often abbreviated and divorced from the content they affect. In an Atomic CSS system, you can tell what the class name does; but there is no relationship between class names—at least, not those used in the stylesheet—and content types.
Let’s illustrate with an example. Below is a set of rules in what we might call a conventional CSS architecture. These rule sets use class names that describe the content to which they apply: a global message box, and styles for “success,” “warning,” and “error” message boxes:
.msg {
background-color: #a6d5fa;
border: 2px solid #2196f3;
border-radius: 10px;
font-family: sans-serif;
padding: 10px;
}
.msg-success {
background-color: #aedbaf;
border: 2px solid #4caf50;
}
.msg-warning {
background-color: #ffe8a5;
border-color: #ffc107;
}
.msg-error {
background-color: #faaaa4;
border-color: #f44336;
}
To create an error message box, we’d need to add both the msg
and msg-error
class names to the element’s class
attribute:
<p class="msg msg-error">An error occurred.</p>
Let’s contrast this with an atomic system, where each declaration becomes its own class:
.bg-a {
background-color: #a6d5fa;
}
.bg-b {
background-color: #aedbaf;
}
.bg-c {
background-color: #ffe8a5;
}
.bg-d {
background-color: #faaaa4;
}
.bc-a{
border-color: #2196f3;
}
.bc-b {
border-color: #4caf50;
}
.bc-c {
border-color: #ffc107;
}
.bc-d {
border-color: #f44336;
}
.br-1x {
border-radius: 10px;
}
.bw-2x {
border-width: 2px;
}
.bss {
border-style: solid;
}
.sans {
font-style: sans-serif;
}
.p-1x {
padding: 10px;
}
That’s a lot more CSS. Let’s now recreate our error message component. Using Atomic CSS, our markup becomes:
<p class="bw-2 bss p-1x sans br-1x bg-d bc-d">
An error occurred.
</p>
Our markup is also more verbose. But what happens when we create a warning message component?
<p class="bw-2 bss p-1x sans br-1x bg-c bc-c">
Warning: The price for that item has changed.
</p>
Two class names changed: bg-d
and bc-d
were replaced with bg-c
and bc-c
. We’ve reused five rule sets. Now, let’s create a button:
<button type="button" class="p-1x sans bg-a br-1x">Save</button>
Hey now! Here we’ve reused four rule sets and avoided adding any more rules to our stylesheet. In a robust atomic CSS architecture, adding a new HTML component such as an article sidebar won’t require adding more CSS (though, in reality, it might require adding a bit more). Atomic CSS is a bit like using utility classes in your CSS, but taken to the extreme. Specifically, it:
- keeps CSS trim by creating highly granular, highly reusable styles, instead of a rule set for every component
- greatly reduces specificity conflicts by using a system of low-specificity selectors
- allows for rapid HTML component development once the initial rule sets are defined
However, Atomic CSS is not without controversy.
The Case Against Atomic CSS
Atomic CSS runs counter to just about everything we’ve been taught on writing CSS. It feels almost as wrong as sticking style
attributes everywhere. Indeed, this is one of the major criticisms of the Atomic CSS methodology: it blurs the line between content and presentation. If class="fl m-1x"
floats an element to the left and adds a ten-pixel margin, what do we do when we no longer want that element to float left?
One answer, of course, is to remove the fl
class from our element. But now we’re changing HTML. The whole reason behind using CSS is so that markup is unaffected by presentation and vice versa. (We can also solve this problem by removing the .fl {float: left;}
rule from our stylesheet, although that would affect every element with a class name of fl
). Still, updating the HTML may be a small price to pay for trimmer CSS.
In Koblentz’s original post, he used class names such as .M-10
for margin: 10px
and .P-10
for padding: 10px
. The problem with such a naming convention should be obvious. Changing to a margin of five or 20 pixels means we’d need to update our CSS and our HTML, or risk having class names that fail to accurately describe their effect.
Using class names such as p-1x
, as done in this section, resolves that issue. The 1x
part of the class name indicates a ratio rather than a defined number of pixels. If the base padding is five pixels (that is, .p-1x {padding: 5px;}
), then .p-2x
would set ten pixels of padding. Yes, that’s less descriptive of what the class name does, but it also means that we can change our CSS without updating our HTML, or without creating a misleading class name.
An atomic CSS architecture doesn’t prevent us from using class names that describe the content in our markup. You can still add .button-close
or .accordion-trigger
to your code. Such class names are preferable for JavaScript and DOM manipulation.
BEM versus Atomic CSS
BEM works best when you have a large number of developers building CSS and HTML modules in parallel. It helps to prevent the kind of mistakes and bugs that are created by sizable teams. It scales well, in part, because the naming convention is descriptive and predictable. BEM isn’t only for large teams; but it works really well for large teams.
Atomic CSS works better when there is a small team or a single engineer responsible for developing a set of CSS rules, with full HTML components built by a larger team. With Atomic CSS, developers can just look at a style guide—or the CSS source—to determine which set of class names they’ll need for a particular module.