I try to help

Converting to Atomic CSS

Merits and pitfalls

Approximate reading time: 6 minute(s)

In this article I'll explore my journey of transforming the bare-bones CSS on my website to Atomic CSS and what the differences I found.

For a long time I've debated whether or not atomic/functional CSS is the way to go for medium to large scale projects. Because I don't have such a project in which to test out my theory, I did the closest thing.

Warning!

Over the course of writing this article I realized I made a few mistakes, these will be explained. Keep reading to learn from my mistake and why current tooling prevented my one important metric from being accurate.

End of warning

First off let's get some of those terms out of the way and answer the question: What is Atomic CSS.

John Polacek, in an article on CSS Tricks defines it as:

Atomic CSS is the approach to CSS architecture that favors small, single-purpose classes with names based on visual function.

Basically what this means is that instead of having:

.button {
  font-size: 1.5em;
  padding: 2em;
}

We will favor:

.font-size-md {
  font-size: 1.5em;
}

.padding-md {
  padding: 2em;
}

But I'm getting head of myself here. First let's discuss another CSS methodology, the widely mentioned: BEM.

BEM

BEM stands for Block, Element, Modifier. This already gives you an idea how this methodology is designed to work.

  • Block: A functionally independent page component that can be reused. In HTML, blocks are represented by the class attribute.
  • Element: A composite part of a block that can't be used separately from it.
  • Modifier: An entity that defines the appearance, state, or behavior of a block or element.

As we can see this is a bit different than the "one class, one rule" that Atomic CSS strives for.

Converting to Atomic

The first thing I needed to do was start creating atoms. I took care in also saving the initial selector where these were applied, this will come in handy later on.

I do have 3 rules which I will exempt from being converted:

html {
  box-sizing: border-box;

  font-size: 16px;

  border-top: 10px solid $color5;
}

* {
  box-sizing: inherit;
}

body {
  min-height: 100vh;
  margin: 1rem;

  word-wrap: break-word;

  font-family: $serif;
  line-height: 1.45;

  color: $default;

  font-feature-settings: 'kern', 'zero', 'tnum';
  -webkit-text-size-adjust: 100%;
}

So, let's begin, shall we?

code {
    display: block;
    position: relative;

    padding: 1em 1em 1em 3.5em;

    border-radius: .3rem;

    overflow-x: auto;
    counter-reset: linenumber;
  }

Turns into

.display-block {
  display: block;
}

.position-relative {
  position: relative;
}

.padding-code {
  padding: 1em 1em 1em 3.5em;
}

.border-radius-code {
  border-radius: .3rem;
}

.overflow-x-auto {
  overflow-x: auto;
}

.counter-reset-linenumber {
  counter-reset: linenumber;
}

Immediately after the second one-off selector I had about the double of lines written. There is a very good reason for which people use a generator to create all of their classes. This also really helps with consistency between paddings and margins, which, should be a core part of any design guideline.

As soon as I got to the more generic parts, I did come to the conclusion that we do repeat a lot of our css rules and there might be something to this Atomic CSS way of thinking.

Time makes fools of us all

Initially this article was due for publishing a few weeks ago, unfortunately this is not the case due to other commitments. These commitments also forced me to make a change in the way I will test. Instead of changing the HTML file's markup, I'll just duplicate the rules (this is where that mistake I mentioned earlier happened).

To clarify:

Before:

.myClass {
  font-size: 20px;
  text-align: center;
}

After:

.myClass {
  font-size: 20px;
}

.myClass {
  text-align: center;
}

Things went smooth and relatively fast after this. It was a very relaxing conversion to boot, so I got a lot of mileage out of this repetitive action.

Measurements - and where it went wrong

Before changes:

  • CSS Size - 5.3Kb - 2Kb gzipped

Atomic CSS

  • CSS Size - 5.5Kb - 2Kb gzipped

No difference in size?

Wait... what!?

This isn't ok. You know it, I know it. So let's examine what happened!

Remember when I said that due to time constraints I had to duplicate selectors in order to make as few changes as possible? Because of this, and the fact that I had a lot of one-off selectors, cssnano merged the rules, effectively nullifying most of the changes I made.

Conclusion

In spite of the issue with my testing scheme, there are a few conclusion I can draw from this experience, flawed as it was.

Handling one-off styles

This can be a pain-point. Thankfully, as we have seen, the tooling is here to mitigate any performance issues one might have with one-off styles.

For those that have noticed that cssnano only does this optimization for adjecent rules and you're afraid this won't happen in your codebase, you could try CSSO. I've personally used CSSO in a few personal projects to promising results.

Consistency and lack there-of makes this approach highly problematic

Obviously if you have 20 padding values then your classes and markup will look quite awful. But consistency will be easier to enforce because developers can pin-point such cases much faster.

Responsive and other conditional styles

These styles have the risk of being very heavy. If you have 3 breakpoints and all of your classes have all three versions, then, obviously your CSS size will suffer.

Benefits
  • Overall CSS size doesn't grow after a certain point, the markup might, but CSS size has a more contained growth.
  • Forces consistency - this might cause friction with any sloppy designer.
  • Highlights inefficient code.
  • Fast prototyping
Drawbacks
  • Due to the nature of "conditional" styles and how this methodology treats them, the size might be significantly larger if used incorrectly
  • You need a way to purge unused rules or regularly clean unused rules
  • HTML needs changes (if you did this properly and you changed your classes, unlike me)

I didn't stick with the Atomic version for two reasons:

  1. I have far too many one-off styles.
  2. Changing anything away from it, might involve a bit more work than needed.
Closing thoughts

From what I've attempted I see two ways of implementing this.

  1. Processed atomic rules Here the end result passed to the browser is in the "atomic" or "functional" form. One rule for one thing.

This one is probably the one that will give you the most theoretical gains at the possible expense of developer experience. 2. Preprocessed atomic rules The end result is not that much different from the CSS you write now. You may not have the same speed gains, but consistency of styles and speed of prototyping are still there.