The 3 Types of CSS Utility Classes

#css #html

A 9.0 earthquake shook my front-end world when I discovered “layout primitives” at every-layout.dev. Then an aftershock rolled through when I learned about fluid sizing and spacing tokens from utopia.fyi. It’s completely changed the way I write HTML and CSS.

Programming is the most fun when I am composing primitives. If those primitives are at the right level of abstraction, programming feels like magic.

In the aftermath of the quake, I am curiously focused on creating the perfect primitive CSS classes to compose in my HTML. In doing so, I have noticed three categories of classes emerging from the stylesheet.

  1. Aesthetic classes
  2. Layout classes
  3. Spacing classes
Note
Before this personal evolution, I thought it best practice to scope and isolate CSS, avoid utility classes, and name everything something semantic. It left me with code that was too coupled, hard to keep consistent, and difficult to understand a few months later. I queried Google for “Alternative to BEM CSS” which led me to Dave Rupert’s article, which led me to Andy Bell’s CUBE CSS. I’ve never looked back.

Aesthetic Classes

The sole concern of an aesthetic class is the look of the element. The “(B)locks” in CUBE. They are specific to the branding of the company or project. They will be named things like: .card, .aside, .quote, .note, .toast, .dialog, .menu, and .tab and contain properties like color:, background:, border:, and box-shadow:. They SHOULD NOT contain things like padding:, display:, or margin:. Those go in the next two types.

Layout Classes

A layout class is solely focused on laying out elements. Not the look (above) and not the spacing between the elements (below). They are usually placed on the parent element and have names like .center, .cluster, .repel, .cover, .stack, .flow, .prose, .scroller, and .switcher. The CSS properties within them should be things like: display:, align-items:, justify-content:, flex-wrap:, grid-template-columns:, and overflow:.

Spacing Classes

Spacing classes are focused only on the space between elements that have been laid out with one of the classes above. If you have never surfed over to utopia.fyi, run as fast as you can to that link to generate fluid spacing and sizing tokens. Once you have those custom properties generated, use Tailwind or SCSS to generate spacing utility classes based on those spacing tokens (or just copy and paste some if you don’t have the tooling).

Spacing classes should have names like .gap-s, .gap-m, .gap-l, .gutter-s, .gutter-m, .gutter-l, .stack-s, .stack-m, stack-l and only contain properties like gap:, margin:, padding:, and their variations.

DO NOT MIX CONTENTS

These CSS concerns are best not to be mixed into the same class. This is where I’ve gone wrong in the past. I used to write CSS like this:

<div class="card">
  <h2>Summary</h2>
  <p>Here you'll find the summary of the complicated data.</p>
  <a href="/details">View Details</a>
</div>
.card {
  background: white;
  border: 1px solid var(--border);
  padding: 1rem;
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);

  & h2 {
    color: var(--brand);
  }
  & p {
    color: var(--text-less);
  }
}

While the HTML looks clean, discovering what the CSS is doing is not immediately obvious. It is highly coupled to the markup and I will certainly be bouncing between to the two files to make changes.

Let’s rewrite it using the three types of primitives described above. Split the aesthetic, layout, and spacing concerns into their own classes and compose a masterpiece in the HTML.

<div class="card-1 stack box gap-xs">
  <h2 class="color-brand">Summary</h2>
  <p class="color-text-less">
    Here you'll find the summary of the complicated data.
  </p>
  <a href="/details">View Details</a>
</div>
/* Asthetic */
.card-1 {
  background: white;
  box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
  border: 1px solid var(--border);
}

.color-brand {
  color: var(--brand);
}

.color-text-less {
  color: var(--text-less);
}

/* Layout */
.stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-s);
}

/* Spacing */
.box {
  padding: var(--space-s);
}

.gap-xs {
  /* Generated by tools (or not) */
  gap: var(--space-xs);
}

Why is this better?

Each of those CSS classes is focused on one thing like all good software components are. Once they are written, I can reuse them over and over in my HTML. If I don’t like the look of “.card-1” for this element, I can swap it out for “.card-2” by changing one character in the HTML class. Once the core primitives are written, I don’t need to leave my HTML nearly as much to style things. It makes development very fun. This is why Tailwind took off.

The Tailwind Phenomenon

For those of you thinking, “This is exactly what I already do with my Tailwind classes.” Well, it’s not quite the same. Tailwind does allow us to stay in the HTML, but in my opinion, its utilities are too primitive. Heydon Pickering has a great article on this.

For example, here I am centering a div with Tailwind.

<div class="mx-auto max-w-5">
  <h1>Too Much Implementation Detail</h1>
</div>

And here it is with a “center” layout class.

<div class="center">
  <h1>Much Easier To Read</h1>
</div>

Tailwind achieves that same thing as the “.center” layout class, but it shows all the implementation details. The “center” class is more descriptive, concise, and easier to remember.

And for those of you thinking, “YUCK, utility classes are ruining front-end web development” like I was, remember composing primitives is what makes this job fun. If you nail the primitives, programming feels like the sun on your back.

Thanks for Reading

Email me your thoughts at kerrto-prevent-spam@hto-prevent-spamey.comto-prevent-spam or give me a mention on Mastodon or X.

If you are interested in personal budgeting software, check out what I'm building at tend.cash.