Getting to know utility-first CSS

Sarah Dayan wrote this convincing article about the qualities of utility-first CSS. Utility-first (or atomic) meaning the main approach to defining CSS classes for every single style property.

Like this:

<h2 class="color-blue font-weight-bold">Heading</h2>

.color-blue{
  color: var(--blue);
}
.font-weight-bold{
  font-weight: bold;
}

If this is a new concept and your gut reaction is ‘eeew that’s basically inline styling’, read that article. As with many front-end techniques, the approach gets a lot of criticism for being different from what people are used to. Because the article appeared nuanced and recommends a utility-first and not a utility-only approach, I wanted to give it a try. If you want to give it a try too, this post may help you avoid some mistakes that I made.

Things I like about utility-first CSS

First of all, I agree with everything Sarah Dayan wrote and it’s too much to repeat here.

The main highlight for me was that with my new utility-based stylesheet, it’s so easy to add a new template or component. In most cases, I don’t have to add new SCSS files. If I need to, the BEM components I add there are much simpler, because most or all of the basic styling is already in utilities. Often I don’t even need to write CSS at all. Although I actually like CSS a lot, it’s fun to edit or create new components this way.

As Sarah Dayan predicted, I now spend a lot less time naming styles. Naming modules is difficult; a good name is descriptive, but descriptive names tend to be long and overly specific. Not having to think about naming anymore is a relief.

An additional benefit of utility-first is that fallback styles can be in one place. By the time I will drop support for a browser, the fallback properties only appear in a very limited amount of lines, where they can be removed easily.

I also like that using values defined by an existing design token is easier than adding a new value—a value that may be inconsistent with the design system. For example, my approach to apply padding to an element is to look for existing classes like .padding-m, .padding-l first. When the value I want to add is not among them, I have to add it. Sticking to existing rules is the default. Compare that with component-based CSS, where I will have to write CSS no matter what, and for that I have to look up SCSS variables with design tokens. Which is more effort and encourages making things inconsistent.

Things I don’t like about utility-first CSS

Although I spent a lot less time naming components, I had to come up with a naming system for my utility styles. I came up with giving all my styles the name of the CSS property, an optional modifier and a value set by a design token. Some examples:

margin-m{
  margin: $m + rem;
}
margin-y-m{
  margin-top: $m + rem;
  margin-bottom: $m + rem;
}
width-100percent{
  width: 100%;
}

That said, with this in place, I barely have to think about naming anymore! Now if you’re using a CSS utility framework (there are lots of them), you will have to learn that framework’s syntax.

A problem I haven’t really solved yet is adding comments. It’s easy to add a comment to a line of SCSS, like:

position: relative; // Because of children's z-index

With utiltiy-first approach I add this to my HTML templates:

<!-- position-relative because of children's z-index -->

When there are comments for multiple properties, that becomes hard to read.

With a component-based approach to CSS, I find it’s easy to know when styles have to be removed from the CSS. In my projects, many components are only referenced once, so when I remove such a reference, I know I have to remove the styles associated with it. When removing a utility class like margin-bottom-m from an element, it’s likely that that class is still in use by another element. But how do I know that I removed the last instance?

A minor problem is that my Jekyll browser refresh setup doesn’t listen to template changes. Where changes in CSS are immediately visible in my browser, I have to make sure the page is completely rebuild when I change the list of utility classes in a template.

Other learnings

I found it difficult to write sensible utility styles for responsive layouts. As such layouts are one-offs most of the time anyway, it made more sense to define them as components. As a result, my utilities have very little responsiveness built in. I have to be careful not to become lazy and skip the responsiveness just to avoid creating new CSS modules!

Occasionally I found myself copying long class lists from one HTML element to the other, but then I realized I was better off creating a component for them. Or, more often, I needed to copy only a few classes from a long list and realized it was so nice I could just pick and choose, rather than having to create a new CSS module with a BEM modifier.

Finally, it takes some getting used to debugging CSS when many style declarations take up three lines in the inspector:

CSS utilities taking up 3 lines for each line of CSS.

Is utility-first CSS better than other approaches?

Next time I start a project, I wouldn’t create a utility-first stylesheet myself and instead pick a CSS utility framework. That would add additional set up, dependencies, syntax and thus complexity, so I’m not sure if I’d do it for a small project.

Applying a utility-first approach to new templates in an existing project turned out to be a bad idea. I spent more than 8 hours converting components to utilities styles for the ForTomorrow website [link]. As these things go, when you have to look at every line of code in a project, in the process I did a lot of other optimizations too. Most of that was simplifying components and making thing visually more consistent. Because of all the refactoring that came with the transition to utility styles, I can’t do a clean before and after test on performance.

Because the transition was a big amount of effort with little improvement visible to users, I wouldn’t say that it was worth it. But I’m happy it’s done now, because its really more fun now to edit templates and create new ones and barely having to add any CSS. And I’m pretty sure that with the utility-first setup it will be much easier and faster to apply the new branding that we’re working on.