frontend May 27

I was wrong about utility-first CSS and here is why!

7 min read –

The approach to working with CSS I am going to write about in this post has a couple of different names it gets referred to. Originally I first saw it called OOCSS (object-oriented), but in recent years you might have come across the terms atomic CSS, functional CSS, or utility-first CSS.

They are all valid names and they make sense from different perspectives, but to keep things simple and easy to follow I am going to pick the last one and stick with it for the rest of the post.

The general idea behind utility-first CSS

We have “traditionally” thought of CSS classes as representing different elements of the UI, and their primary purpose was to describe all of the styles of a particular element in a single place between those curly brackets inside our CSS files.

Utility-first flips that idea on its head and uses classes not to define different components, but rather to provide us with a toolbox of different helper classes (utilities) that we can mix together to style a component by applying them to its HTML element.

Compare the two examples below to see the idea in action

.card{
    padding: 10px;
    background: white;

    &__title{
        font-size: 20px;
    }
}
<div class="card">
    <h2 class="card__title"></h2>
</div>
<div class="p-10 bg-white">
    <h2 class="text-lg"></h2>
</div>

Benefits

Let’s list a couple of things I learned to appreciate after the first couple of months after switching to utility-first with Tailwind.

Maintain and load less CSS

There was a talk given at dotCSS by Adam Detrick making the point that the solution for CSS at scale is writing less CSS, and this seems to be very obvious and powerful once you start using it.

With utility first classes you can write 90% less CSS and that means that both the performance and maintainability cost of scaling CSS are going to be at least 10 times smaller.

Productivity

So writing less CSS translates to better maintainability and better performance but it also means that we can get the same results by typing less overall, we write a lot less CSS, but we do need to write a bit more class names in our HTML.

Component classes

// .css
.item{
  padding: 10px 10px 20px;
  margin: 20px 30px;
}

// .html
<div class="item"></div>

vs utility classes

<div class="p-10 pb-20 mx-20 my-30"></div>

Note that we don’t need to switch between different files in the utility-first approach to do the same thing and that also results in an easier workflow.

The theme layer

Tailwind gives us powerful configuration options and full control over customizing the generated classes according to our own designs.

It maps perfectly to low-level primitives of a design system;

  • color scheme (eg. bg-primary-300, border-gray-100)
  • typography (eg. text-lg leading-tight)
  • grid system (eg. w-1/12)
  • spacing (eg. m-10, p-6)

By separating the theme layer like this we hugely improve our ability to make global design changes in the future and increase our ability to reuse code over different projects with completely different designs. We can also incorporate third party components and layouts built with Tailwind from other authors while easily applying our own visual styles.

Some parts of the design system can also directly be exported from design tools like Figma, Sketch, or Adobe Experience Design to a tailwind config file, which seems to me like a huge leap forward in the process of design to code delivery.

The ecosystem

There are a couple of competing implementations of utility-first methodology with different frameworks active now, to mention a couple we have Turretcss and Tachyons, but I’ve only been exploring Tailwind CSS for now, and I’ll just share a couple of links to cool stuff I found in their ecosystem.

I would first invite you to start out with Tailwind Toolbox which contains interesting tools, ready-made templates, components, and different Tailwind plugins expanding its core functionalities.

If you are just looking for ready-made components try with these first; Tailwind Components or Tailwind UI, but to get an overview of the whole Tailwind ecosystem, the best place to go would be Awesome Tailwind on GitHub.

Also, I want to especially draw your attention to this tool Tailwind builder to get an idea of how combing existing components and configuration options in the theming layer could fundamentally change the way we build UI’S.

Reasons I had for being skeptical of using utility-first

Before starting to use tailwind, I did find the idea interesting and used utilities here and there, but I had a few objections against it that kept me from fully embracing it;

  • Using single-purpose utility classes seemed the same as using inline styles, and that was generally considered a bad practice for a very long time
  • There is no separation of concerns between the structure and the styles
  • Writing responsive styles with utility classes like .atMedium–display-inline-block, seemed overly clumsy and hard to read
  • There were various different ways to set the naming convention of utility-first libraries and no clear vision of which approach would survive in the long term and still be relevant for projects that need to be maintained and scaled up for years to come.

The author of Tailwind wrote a great article where he reasoned against the issues raised about inline styles and separation of concerns, so go ahead and check that out if you have similar concerns yourself.

I would only additionally address the last two items from the list above. It seems to me that what tailwind did was to refine the naming convention by making it as short and as simple as possible, which solved the issues with the last two points.

If you watched the “write less CSS” talk I linked above, you could see class names that looked like .atMedium–display-inline-block, which seems far too explicit to be useful.

In sass we could do the same with @media($md){display: inline-block;), which would actually be better because we would add the media query only once instead of repeating .atMedium– over and over again for each individual property we want to set.

Here is a common responsive pattern of stacking blocks solved with tailwind classes

<div class="flex flex-col md:flex-row text-sm md:text-md">
    <span>Item 1</span>
    <span>Item 2</span>
    <span>Item 3</span>
</div>

Making the class names as simple and as small as possible made them both easier to read and easier to write, and I believe any further development in the naming conventions can only make the classes smaller and simpler and there is a definite limit on how far that can go before becoming meaningless. There is an Einsteins quote that comes to mind in regards to this.

Everything should be made as simple as possible, but not simpler

Albert Einstein

After seeing how useful utility first has been in the last couple of projects I used it I started to doubt my original arguments against it, not in the way meaning they were simply not strong enough but rather that they were maybe made in bad faith, to begin with.

The real reason why

It seems to me that I used the technical objections as excuses for not switching to utility-first sooner, but I now think that the real reason was not technical at all, but rather a psychological one, namely the fear of obsolescence.

“Obsolescence is the state of being which occurs when an object, service, or practice is no longer wanted even though it may still be in good working order.”

All of the tooling and the best practices in a field always rely on some basic underlying assumptions, and if those basic assumptions are challenged, everything built on top of them can crumble very fast.

In the shift that happened with utility-first, the very role of classes was changed, in a way that made problems we were solving with, for example SASS or BEM almost completely irrelevant.

Once we have invested our time and effort into learning and practicing particular methods and solutions we tend to overlook that the whole problem space might have shifted and that the problems we keep solving might not even need to be solved anymore.

When that happens we might find ourselves resisting new ideas, tools, and approaches, because we don’t want to face the fact that some of the skills and knowledge we made efforts to acquire in the past are not applicable to new solutions anymore.

Our job is to implement innovation

Our age is an age of unprecedented innovation in which the web development industry has a particularly high pace of change because of its underlying open-source technology and huge online communities involved and participating in pushing the web forward.

I’ll borrow an Idea from Peter Theil’s book Zero to One in which he presents a graph with two different axes changing the world today.

On one axis we have the movement of innovation, going from zero to one, and on the other one we the movement of globalization; going from one to N.

A small percentage of developers are the ones going from zero to one, creating new tools and methodologies, but the role for most of us in the industry is to apply the things someone else has created to further improve existing projects or build new ones with the least amount of technical debt.

More often then not, progress will not move linearly, and in the long run, there will be shifts in each of our careers in the ways we do things that will make some of the parts of our knowledge irrelevant if we want to remain competitive and bring the best possible value to projects we are working on.

A lesson to be learned

I wanted to share this experience of making a significant change in the way I worked while it was still fresh to me and I wanted to make a point about how important I think it is to address these psychological factors I believe we all suffer from to some extent.

The best lesson I can get out of this experience is to try to be skeptical of my own reasoning and biases behind it and to try to put my prejudices aside and just go ahead and experiment with new tools and ideas as much as I can, after all, once we get over the initial resistance that usually turns out to be pretty fun.

Once you can directly see the benefits in your own workflow it will be a lot easier to let go of the things you learned before and a lot harder to think of strong enough reasons to justify the delay of the change.