Why I'm in the Tailwind cult

Phoenix on Rails is a comprehensive guide to Elixir, Phoenix and LiveView for developers who already know Ruby on Rails. If you’re a Rails developer who wants to learn Phoenix fast, click here to learn more!

Recently an article titled Why Tailwind Won hit the front page of Hacker News, kicking off a heated debate about this polarizing CSS framework. Some developers can’t get enough of Tailwind; to others, it’s a blight upon our profession and the worst thing since the <blink> tag.

Haters be damned. I love Tailwind, I’m using it on every project where I have the choice, and I’d be happy to never write a line of vanilla CSS again. According to one commenter this means I’m in a cult, but hey, I’m happy, and this Kool-Aid tastes delicious. Here’s why I’m letting Tailwind fill my sails.

I’ve been building websites for something like twenty years now, since I first learnt basic HTML at the age of roughly twelve. From an early age I understood that you should never, ever, ever write your HTML like this:

<div style="background-color: #333; border: 1px solid #ccc;">
  <a style="padding: 5px 10px; color: #fff;" href="/crash">Elbow grease</a>
  <a style="padding: 5px 10px; color: #fff;" href="/bang">Striped paint</a>
  <a style="padding: 5px 10px; color: #fff;" href="/wallop">Long weight</a>

These so-called “inline styles” are verbose, ugly, repetitive, and generally read like shit. The correct approach, so I was taught, is to replace those style attributes with a meaningful “semantic” identifier, typically class:

<div class="menu">
  <a class="menu-link" href="/crash">Elbow grease</a>
  <a class="menu-link" href="/bang">Striped paint</a>
  <a class="menu-link" href="/wallop">Long weight</a>

… and then to extract my styles to a tidy CSS file:

.menu {
  background-color: #333;
  border: 1px solid #ccc;

.menu-link {
  padding: 5px 10px;
  color: #fff;

And thus you’re on the path to enlightenment. Master the art of Cascading Style Sheets - in particular the “cascading” part, which refers to the hierarchical system by which style rules are inherited or overridden - and you’ll be writing clean, composable, reusable, readable, maintainable stylesheets that are a pleasure to work with and a joy to behold.

That’s what they told me I would discover. But twenty years later, I’m still searching.

I don’t doubt that somewhere out there, a better programmer than me is using CSS as intended to get fantastic results. More power to them, but my own CSS workflow involves fiddling around hopelessly in the Chrome inspector, darting from rule to rule trying to remember whether style X from rule Y with selector Z takes precedence over style P from rule Q with selector R or if it’s overridden by rule A inherited by style B on element C, so I tweak my code but it breaks component M because I accidentally overrode rule N, and the cycle repeats, and my stylesheets keep getting bigger and shittier, and I proceed at a snail’s pace while wishing I could be working on anything else. Sass helps a little, but it’s like taking aspirin for a bullet wound. I’ve tried, I really have - but I just can’t get “cascading” to be anything less than agonizing.

Then I found Tailwind, and saw that the answer, all along, was inline styles:

<div class="bg-gray-800 border border-gray-300">
  <a class="px-2 py-1 text-white" href="/crash">Elbow grease</a>
  <a class="px-2 py-1 text-white" href="/bang">Striped paint</a>
  <a class="px-2 py-1 text-white" href="/wallop">Long weight</a>

Tailwind achieves the impossible: it takes inline styles and it makes them usable. It’s an all-encompassing set of utility classes that directly map onto the CSS rules you already know, with some bells and whistles to make things like :hover and !important work too. I know how terrible it looks at first - I know how flagrantly it violates everything you were taught about the correct way to style your app - but when I gave it a chance, I was surprised by how quickly it overcame my objections.

Unreadable? Maybe at a glance, but you quickly get used to it. The naming conventions are consistent, intuitive, and you can learn how to read (and write) them very quickly. If you can read CSS, you can read Tailwind - it’s just a set of abbreviations for the language you already know, like m for margin and p for padding. Moving from regular CSS to Tailwind isn’t like moving from English to Chinese - it’s like moving from handwriting to touch-typing.

Some accuse Tailwind of “mixing presentation and content”, a cardinal sin, but I say “meh”. I fully agree that presentation logic should be kept separate from content, but most of my “content” isn’t in my HTML anyway; it lives in the database and is output to the template using something like <%= @this %>. What’s left is a bunch of HTML tags that describe how to structure, arrange and present this information to the user - and with Tailwind, the full presentational logic now sits in a single place, as a single, coherent unit that I can grok from one file. Why did I ever think it was a good idea to keep things separate?

Using “semantic identifiers” is another of those academic ideas that I’ve never seen survive contact with reality. They say your HTML tags should have meaningful names like .navbar-dropdown or .user-email-address that describe what the element is rather than what it looks like, but don’t they know that “naming things” is one of the two hard problems in computer science? Most of my <div>s don’t have an obvious name, so my “semantic” labels have to get longer and more contrived - .navbar-dropdown-inner-container-upper-icon-wrapper - until, inevitably, words like “left” or “dark” or “margin” start seeping back in, and I might as well be writing Tailwind.

When there is an obvious semantic identifier, it’s usually already in my code anyway - e.g. as the name of the React component (<NavbarDropdown />) or the function in my Phoenix view (<.navbar_dropdown />.) So why write it again? With Tailwind I no longer have to waste mental energy inventing “semantic” names for tags that don’t need them - I can skip straight to the styling, and it’s liberating.

Repetitive? Yes, Tailwind can be very repetitive when you’re thrashing out something new, but first drafts are always shitty. It’s 2023 - no-one is writing static .html files, and I write Tailwind the same way I write everything else: I make it work, then I go back and clean it up using normal programming constructs. I don’t need “cascading” to keep my Elixir code DRY; why reinvent the wheel?

<div class="bg-gray-800 border border-gray-300">
  <%= for {href, text} <- [
    {"/crash", "Elbow grease"},
    {"/bang", "Striped paint"},
    {"/wallop", "Long weight"},
  ] do %>
    <a href={href} class="px-2 py-1 text-white"><%= text %></a>
  <% end %>

So to the person who accused me and my cult of “actively fight(ing) the engineering choices behind the web platform”, I plead guilty. If those choices work for you, great, but I’ve been trying to make them work myself for twenty years now, and the supposed benefits of “cascading style sheets” still haven’t materialized for me. Maybe I’m just incompetent, but I give up: from now on I’m pushing the problem up to my application code, where I can use the tools that actually work. Sue me.

There are some other things I like about Tailwind, but what difference will it make? No-one ever changed their mind about Tailwind just from reading a blog post. It might be the most polarizing technology I’ve seen in my entire career. If anything I’ve written here enrages you, please make sure to share this article as widely as possible so that everyone can know how wrong I am. Thanks! And tell them to buy my Phoenix course, which barely mentions Tailwind.

Photo by Austin Neill on Unsplash.

Learn Phoenix fast

Phoenix on Rails is a 72-lesson tutorial on web development with Elixir, Phoenix and LiveView, for programmers who already know Ruby on Rails.

Get part 1 for free: