Note: The header image obviously isn’t Tailwind CSS, it’s just there for adornment and you pointing it out doesn’t make for good commentary.

After watching the completely ridiculous drama of Tailwind vs Vanilla CSS unfold over time, I’ve finally got around to actually use Tailwind CSS. If you don’t already know, Tailwind goes 100% all-in on pre-made utility classes–one class per CSS declaration–which allows developers to style most things on the HTML elements themselves rather than going back and forth between templates in Views/ and CSS files in /wwwroot for example.

As you can imagine, one class per declaration can make your HTML look very verbose (snippet taken from Tailwind CSS’s homepage):

<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">
        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>

For the longest time I preferred doing things in vanilla CSS, and still do, so I didn’t pay Tailwind any mind one way or another. I enjoy CSS in spite of its warts, weirdness, and outright mistakes, and I did have similar arguments against Tailwind such as its aforemntioned verbosity that appeared to not lend itself to maintainability, and it seemingly not being different from using the style attribute.

I say “appeared to” and “seemingly” because, truth be told, it only appeared that way in my mind and my preconceived way of doing things. I didn’t actually use it and experience it for myself.


So I decided to use Tailwind and actually get a feel for it.


Guess what? I liked it. It took some time to get used to the naming conventions, but with any decent auto-complete feature in your editor of choice it clicks rather fast. As a dotnet developer, I normally do typical MVC-style apps, and only having to style things in the .cshtml template view files makes things go by much faster.

And unlike other frameworks like Bootstrap or Bulma, you aren’t learning some bespoke way of styling outside of just knowing the class names, you just need to know how CSS works. Tailwind doesn’t abstract away the underlying way that styles are applied or how flexbox or grids work, it just:

  1. Keeps styles on the HTML elements themselves, going off on the principle of locality of behavior in the same vein as HTMX and;
  2. Keeps the infamous, aggressive, and annoying “cascading effect” in check by sacrificing terseness

Now, I know proponents of vanilla CSS will at least say for point 2 that modern CSS has a lot of options for managing cascade: @layers, containers, nesting, @scope, etc. really does go a long way in preventing styles from unintentionally leaking to other elements, really neat stuff.

However, a lot of that has to be handled up front, and if you’re a back-end dev that just wants to make something look decent, or on a team where spot-changes need to be made and the front-end folks are busy with something else, jumping into the CSS is like playing Operation and takes away time that ought to be spent on more interesting or more pressing problems.

Also, given that most frameworks like ASP.NET Core leverage templating, partial views, etc. the verbosity doesn’t get in the way as it may first appear when you look at a snippet of server-side-rendered HTML like the one above. Making edits aren’t as precarious as you know that adding or removing a class will affect only one declaration for that element.

(side-note: For all the praise that the cascade gets in some CSS circles, there’s sure a lot of options that exist to mitigate it. It’s really like electricity in a live wire with no resistance. It’s not that it’s intrinsically bad, just that its “runaway” nature is more frustrating than helpful at times…imagine if at the outset of its creation, the cascading effect was opt-in in some way)

As far as the charge of Tailwind being a glorified style attribute, I found this to not be true as well. It may appear so at first glance, as properties that can contain near endless amounts of values (e.g. margin, colors, padding, etc.) can potentially be populated all over the place with no rhyme or reason. With Tailwind, the “surface area” of values are dramatically reduced to a set of discrete options. You don’t specify colors, for example, with rgb(R, G, B), but rather with class names like text-indigo-50, text-indigo-100, text-indigo-700, and so on. If you don’t have a preexisting design system of your own and are okay with reducing the amount of possible values this is certainly a good trade-off and not at all like chaotically raw-dogging CSS.

But this is what I really wanted to get at: The Apparent vs The Actual. It only appeared to me that Tailwind didn’t solve the problems it purports to have solved. It only seemed to me that, if people “just learned CSS and did it correctly” we wouldn’t need to have all these frameworks and complexity.

It was only after sitting down and actually implementing it on a project did I realize what the hype was all about. Does it “solve” everything? No, and I don’t think the Tailwind team ever said or implied that in the slightest. For more exquisite designs, you may still need to leverage vanilla CSS and/or JS libraries like d3.

The only thing so far that I’ve run into as a “pain point” is having to change certain properties like colors on a wide scale, although a simple find-and-replace or grep appears to mitigate that fine.

What Tailwind really excels at is getting you to 80%+ of the way there with none of the aggressive cascading, and none of the styling declarations scattered around many CSS files like a fine mist.

So I’d say, try it. It doesn’t have be implemented on a project at work, you don’t have to refactor your front-end this instant, but you could just create a side-project locally and fiddle around with it. Especially if you’re like me and thought, “I just don’t see it”.