Skip to main content Accessibility Feedback

What's your problem with Tailwind?

Last week, I went on a bit of a Twitter rant about Tailwind, which led a handful of people to ask me…

What’s your problem Tailwind?

Today, I wanted to talk about that. Let’s dig in!

Utility classes are great!

Tailwind is a utility class library.

What are utility classes? Generally speaking, they’re CSS classes that are generic in nature and modify or style just one thing. They’re not tied to a specific component, but to a specific style property.

For example, in my own CSS, I have utility classes for changing the alignment of text, swapping font families, and nudging and tweaking the margin.

/**
 * Utility class examples
 */
.font-sans {
	font-family: "PT Sans", sans-serif;
}

.font-serif {
	font-family: "PT Serif", serif;
}

.text-center {
	text-align: center;
}

.margin-bottom-large {
	margin-bottom: 2em;
}

In my HTML, I use utility classes to make little changes that deviate from the norm for my components.

<h1 class="font-serif text-center">Some special heading</h1>
<p>Some regular old paragraph text.</p>
<p class="margin-bottom-large">Another paragraph, with a HUGE margin below.</p>

Truly, utility classes are amazing!

Utility classes are not so great for building an entire UI

The Tailwind website includes examples of entire UI components built with utility classes, like this…

<figure class="md:flex bg-slate-100 rounded-xl p-8 md:p-0 dark:bg-slate-800">
  <img class="w-24 h-24 md:w-48 md:h-auto md:rounded-none rounded-full mx-auto" src="/sarah-dayan.jpg" alt="" width="384" height="512">
  <div class="pt-6 md:p-8 text-center md:text-left space-y-4">
    <blockquote>
      <p class="text-lg font-medium">
        “Tailwind CSS is the only framework that I've seen scale
        on large teams. It’s easy to customize, adapts to any design,
        and the build size is tiny.”
      </p>
    </blockquote>
    <figcaption class="font-medium">
      <div class="text-sky-500 dark:text-sky-400">
        Sarah Dayan
      </div>
      <div class="text-slate-700 dark:text-slate-500">
        Staff Engineer, Algolia
      </div>
    </figcaption>
  </div>
</figure>

And this…

<div class="flex font-sans">
  <div class="flex-none w-48 relative">
    <img src="/classic-utility-jacket.jpg" alt="" class="absolute inset-0 w-full h-full object-cover" loading="lazy" />
  </div>
  <form class="flex-auto p-6">
    <div class="flex flex-wrap">
      <h1 class="flex-auto text-lg font-semibold text-slate-900">
        Classic Utility Jacket
      </h1>
      <div class="text-lg font-semibold text-slate-500">
        $110.00
      </div>
      <div class="w-full flex-none text-sm font-medium text-slate-700 mt-2">
        In stock
      </div>
    </div>
    <div class="flex items-baseline mt-4 mb-6 pb-6 border-b border-slate-200">
      <div class="space-x-2 flex text-sm">
        <label>
          <input class="sr-only peer" name="size" type="radio" value="xs" checked />
          <div class="w-9 h-9 rounded-lg flex items-center justify-center text-slate-700 peer-checked:font-semibold peer-checked:bg-slate-900 peer-checked:text-white">
            XS
          </div>
        </label>
        <label>
          <input class="sr-only peer" name="size" type="radio" value="s" />
          <div class="w-9 h-9 rounded-lg flex items-center justify-center text-slate-700 peer-checked:font-semibold peer-checked:bg-slate-900 peer-checked:text-white">
            S
          </div>
        </label>
        <label>
          <input class="sr-only peer" name="size" type="radio" value="m" />
          <div class="w-9 h-9 rounded-lg flex items-center justify-center text-slate-700 peer-checked:font-semibold peer-checked:bg-slate-900 peer-checked:text-white">
            M
          </div>
        </label>
        <label>
          <input class="sr-only peer" name="size" type="radio" value="l" />
          <div class="w-9 h-9 rounded-lg flex items-center justify-center text-slate-700 peer-checked:font-semibold peer-checked:bg-slate-900 peer-checked:text-white">
            L
          </div>
        </label>
        <label>
          <input class="sr-only peer" name="size" type="radio" value="xl" />
          <div class="w-9 h-9 rounded-lg flex items-center justify-center text-slate-700 peer-checked:font-semibold peer-checked:bg-slate-900 peer-checked:text-white">
            XL
          </div>
        </label>
      </div>
    </div>
    <div class="flex space-x-4 mb-6 text-sm font-medium">
      <div class="flex-auto flex space-x-4">
        <button class="h-10 px-6 font-semibold rounded-md bg-black text-white" type="submit">
          Buy now
        </button>
        <button class="h-10 px-6 font-semibold rounded-md border border-slate-200 text-slate-900" type="button">
          Add to bag
        </button>
      </div>
      <button class="flex-none flex items-center justify-center w-9 h-9 rounded-md text-slate-300 border border-slate-200" type="button" aria-label="Like">
        <svg width="20" height="20" fill="currentColor" aria-hidden="true">
          <path fill-rule="evenodd" clip-rule="evenodd" d="M3.172 5.172a4 4 0 015.656 0L10 6.343l1.172-1.171a4 4 0 115.656 5.656L10 17.657l-6.828-6.829a4 4 0 010-5.656z" />
        </svg>
      </button>
    </div>
    <p class="text-sm text-slate-700">
      Free shipping on all continental US orders.
    </p>
  </form>
</div>

Proponents of Tailwind argue that this way of authoring HTML is much easier and faster because you “never have to touch CSS.”

I’ve built UIs like this! It is faster during the prototyping phase… And then there inevitably comes a time where I need to update the style.

Now, instead of just making a single change on a single class in a CSS file, I make a dozen little changes across numerous HTML elements scattered across many pages.

That figure quote component example above? You could rewrite that like this…

<figure class="quote-box">
	<img src="/sarah-dayan.jpg" alt="" width="384" height="512">
	<blockquote>“Tailwind CSS is the only framework that I've seen scale on large teams. It’s easy to customize, adapts to any design, and the build size is tiny.”</blockquote>
	<figcaption>
	  <div class="quote-box_name">
	    Sarah Dayan
	  </div>
	  <div class="quote-box_title">
	    Staff Engineer, Algolia
	  </div>
	</figcaption>
</figure>

Did I have to spend a bit more time writing CSS to do this? Sure, I guess, assuming I already had utility classes for every single style choice I wanted for this component.

Is the HTML now substantially easier to write and maintain? FUCK YES!

Even Tailwind creator Adam Wathan acknowledges this in his treatise on utility-class first UI…

One of the areas where my opinion differs a bit from some of the really die-hard functional CSS advocates is that I don’t think you should build things out of utilities only…

I still think there are a lot of use cases where it’s more practical to create a CSS component than it is to create a template-based component.

For the sort of projects I work on, it’s usually simpler to create a new .btn-purple class that bundles up those 7 utilities than it is to commit to templatizing every tiny widget on the site.

…but build them using utilities first

The thing is, though, that’s not what the Tailwind website (or it’s die-hard fans) typically advocate for. I mean, just look at those examples copy/pasted directly from the Tailwind site!

No premature abstractions

Again, from Adam’s article on utility-first design…

Taking a component-first approach to CSS means you create components for things even if they will never get reused. This premature abstraction is the source of a lot of bloat and complexity in stylesheets.

I agree with this, and you see in in JavaScript all the time, too!

But a tool like Tailwind doesn’t really fix that. It just swaps one abstraction—components—for another one—utilities.

Use the platform

Just to reiterate: I love utility classes, and I love the idea of avoiding premature abstraction.

But as is the case for so many big tools that try to be all the things to all the people, Tailwind has simply swapped one set of challenges for another.