TL;DR... Skip to conclusion

If you aren't familiar with the term 'Utility-first CSS' it's basically just a way to apply CSS styles to your site. Conventionally, a class name is added to an item, and then the appropriate styles are added to that class in the CSS. In the Utility-first universe, classes are created first and the appropriate class names are then added to the item's HTML as necessary.

Conventional CSS Utility-first
<h1 class="primary-heading">
  My Classic CSS Heading
</h1>
<style>
  .primary-heading {
    background-color: white;
    color: var(--color-primary, #333);
    font-family: Arial, sans-serif;
    font-size: 2.8rem;
    font-weight: 400;
    line-height: 1.5;
    margin: 32px 0;
    padding: 0 0 10px 0;
  }
  @media (min-width:768px) and (max-width:1024px){
    .primary-heading {
       text-align: center;
    }
  }
</style>
<h1 class="background-white
    color-primary
    font-family-arial
    font-size-xxl
    font-weight-normal
    line-height-normal
    margin-horizontal-nil
    margin-vertical-sm
    padding-bottom-xs
    text-align-center→tablet">
  My Utility-first Heading
</h1>
<style>
  .background-white {
    background-color: #fff;
  }
  .color-primary {
    color: var(--color-primary, #333);
  }
  .font-family-arial {
    font-family: Arial, sans-serif;
  }
  .font-size-xxl {
    font-size: 2.8rem;
  }
  .font-weight-normal {
    font-weight: 400;
  }
  .line-height {
    line-height: 1.5;
  }
  .margin-horizontal-nil {
    margin-left: 0;
    margin-right: 0;
  }
  .margin-vertical-sm {
    margin-bottom: 32px;
    margin-top: 32px;
  }
  .padding-bottom-xs {
    padding-bottom: 10px;
  }
@media (min-width:768px) and (max-width:1024px){
  .text-align-center→tablet {
      text-align: center;
  }
}
</style>

So crazy right? We just managed to make a single element as complex as possible! We created nine CSS classes where two did perfectly well.

Ok, so that looks like a headache in the making, so why do it? Well, I've been hearing good things about Utility-first CSS for some time, so I decided to refactor a website to use utility classes and see if it lived up to the hype.

The starting line

To set the context, in some systems adding class names to the HTML can be quite a challenge and in others it's easy. The site I looked at is built with React and amending class names is really easy. Also, passing class names as parameters to child elements and setting class names based on state variables are also straightforward so a good candidate for this test. The site uses Bootstrap 4 grid system which I wasn't going to touch. The existing CSS was pretty classic, written in SCSS. Since I was going to refactor, seemingly with the sole aim of making the HTML as ugly as possible, I decided to implement BEM (Block Element Modifier) for bespoke styling and utility classes for generic styles.

Before After
.footerNavigation {
  .linksList {
    ...
    .linksItem {
      ...
    }
  }
}
.footer-nav {
  &__list {
    ...
  }
  &__item {
    ...
  }
}

The reason for using BEM wasn't only to make the class names ugly, it was to reduce the cyclomatic complexity as much as possible. I wanted to try and make the selectors really simple aiming for just one level. To that end I didn't use BEM syntax modifiers at all, preferring instead to add simple state and variant classes directly to the elements that needed them.

The refactoring process

Firstly, I added the BEM class names to a component. Then, I created a new SCSS file and moved over the CSS from the existing SCSS, removing things that could be added as a utility class and adding the utility classes to the HTML as I went. All the while, keeping an eye on the UI with BackstopJS. Once all the component's CSS was moved and the utility classes added I removed the original class names and deleted the original SCSS.

Here is an example SCSS of a small list item component before and after refactoring, when all the padding, margin, border, typography etc... styles had been moved to utility classes in the HTML.

Before After refactoring
.productListItem {
  border-top: 1px solid var(--color-2);

  @include respond-to(tablet) {
    border-top: 0 none;
  }

  .singleProduct {
    color: var(--color-paragraph);
    display: inline-block;
    font-family: var(--font-paragraph);
    font-size: 1.2rem;
    line-height: 1.8rem;
    margin: 0 30px 0 0;
    padding: 30px 0 20px 0;
  }

  .singleProductLink {
    display: block;
    color: var(--color-black);
    text-decoration: none;
    width: calc(100% - 30px);
  }

  .singleProductIcon {
    float: left;
    margin: 0 30px 0 20px;
    position: relative;
    top: -2px;
  }

  .singleProductText {
    display: block;
    margin-left: 50px;
  }

  .fileTitle {
    border-bottom: 2px solid var(--color-4);
    text-transform: capitalize;
  }

  .fileExtension {
    color: var(--color-grey);
    font-size: 1rem;
    text-transform: uppercase;
  }

  .fileSize {
    color: var(--color-grey);
    font-size: 1rem;
  }
}
.product-list {
  &__icon {
    float: left;
    left: 16px;
    position: relative;
    top: -2px;
  }

  &__link {
    width: calc(100% - 30px);
  }
}

This was a fairly typical outcome, the amount of CSS was reduced, no nested class names. The HTML took the hit with the amount of added class names. But actually, I didn't think it was too bad. After refactoring the above component looked like this... (previously there was a single class name on each element)

<li className="product-list__item border-top_xs-grey-25 tablet↑border-nil">
  <p
    className="product-list__file p_bottom_xxs p_top_xs body_2 m_nil m_right_xxs inline-block"
  >
    <a
      href="{file.uri}"
      className="product-list__link text-decoration_none color_black block"
    >
      <span className="product-list__icon icon"></span>
      <span className="product-list__text m_left_lg block">
        <span
          className="product-list__file-title text-transform_capitalize border-bottom_sm-grey-40"
        >
          {product.title}
        </span>
        <span
          className="product-list__file-ext body_3 text-transform_uppercase color_grey-85"
        >
          {file.extension}
        </span>
        <span className="product-list__file-size body_3 color_grey-85">
          {file.size}
        </span>
      </span>
    </a>
  </p>
</li>

The outcome

So, all in all, it was an interesting exercise, the more I did the more I liked it.

Pros

There were more pros than I was expecting...

Faster development

There are a number of reasons why I think development would be quicker...

  • The amount of bespoke CSS needed to support each component is smaller
  • Having utility class names for common visual elements/treatments (e.g. box-shadow) very quick and handy
  • Simple components hardly needed any bespoke CSS at all
  • Fewer unpleasant surprises, e.g. if a component is used by another component and displays differently, in React at least, it was much easier to amend the class names, rather than overriding the styles in the CSS

Simpler CSS

The resulting CSS output was simpler

  • Almost all selectors ended up at a single level
  • The CSS was easier to understand as the complexity was reduced
  • Fewer CSS specificity issues, the vast majority of selectors were single level so overriding a rule was easy and rarely necessary

Enforced consistency

A number of similar elements had sight variations in font-size, letter-spacing, line-height, margins, paddings, box-shadow, transitions etc... these were standardised by creating utility classes for each variant.

Lower entry barrier

In my opinion, onboarding new UI developers or handing over the site to another team would be easier as the initial downside of the messy HTML would quickly be mitigated by the simplicity of the CSS.

Cons

As far as the downsides go, they are a couple of things...

Uglier HTML

It's a bit like walking into a teenagers bedroom, at first glance it just looks like random mess! But actually, if you just relax there are some benefits. The fact that you can tell how an element will display just by looking at the HTML is powerful. Should an item have centre aligned text on tablet? Should it have a bigger font on mobile? You can match the spec with the class names in the HTML without having to constantly check the output in a browser. Beauty is in the eye of the beholder!

Heavier HTML

In my test the HTML ended up marginally heavier (2.5% approx), this would have been bigger if I'd used long-hand class names from the start, even so I don't think this would be an issue. But, if this did prove to be a problem one could consider minifying the class names on the production environment.

More class declarations overall

I guess the biggest consideration for me would be the need to implement a CSS purge of some sort. For example, I created padding utility classes p[_side][_size][↑@mq][→@mq-range] this gave me about 400 classes. I didn't audit what I actually used, but to hazard a guess, I probably used about 24 all together! So implementing a CSS cleaner to remove the unused declarations would be a must. Also, any class names coming from the content or in some way content author manageable would need to be whitelisted somehow.

Apart from that, there wasn't really anything that I could get too upset about.

Take aways

Would I use Utility-first CSS on a project? Yes, 100% yes!

For the naming convention I started off refactoring with short class names, but after continuously forgetting what they were and having to look them up again and again, I came round to the longhand format. For typographic styles I didn't use individual class names for font-family, font-size etc... Instead, I created classes containing basic typography styles with the format [type]-[variant] e.g. body-1, body-2 etc... these contain typographic styles only, no colour or background, just font-*, line-height, letter-spacing etc... this proved to be very quick and easy to implement and debug.

All things considered, Utility-first CSS gets the thumbs-up from me. I look forward to embracing it on future projects! : )

Further reading