2023-09-29Headwind

There seems to be a little swirl of anti-Tailwind sentiment brewing. Which is fine! All fine, I assure you. Tailwind is intensely polarizing and I don't want to harsh anyone's mellow. But I'm honestly getting sort of sick of the "skillful and experienced developers don't like Tailwind because it holds them back" trope because it just ain't true. I read a long and pretty entertaining piece yesterday that argued essentially that: Tailwind is a crutch for beginners that blunts the edge of experts. Last week I read another post that, interestingly, used the same craftsmanship metaphor.

Let's put the boot in

One of the articles namedrops effin' Bootstrap. The other says, and I quote,

"you can tell most websites built with [Tailwind] at a glance."

I think that's where my cup finally ran over and I stumbled over to this blog with tears streaming down my face because that's just super-duper-not-true, and, ya know, comparing Bootstrap to Tailwind is like comparing apples to semiconductors. You can do it, I suppose: one is crunchy and tangy and the other has interesting electrical properties, but I don't know that the comparison gained us all that much wisdom?

Let the record also show that I never got along with Bootstrap. Everything made with it turned out samey and bland and I was always fighting some offbrand margin or weird ragged left edge after dutifully trying to copy-paste the official components. Customization was a dull trial-and-error slog through preprocessor variables. The final injustice was the fact that I had to bludgeon my users with a 160kB (minified!) CSS file for the luxury of using it. Bootstrap's one redeeming quality was its standardization: like the CSS Zen Garden of yesteryear, you could rely on certain pre-determined markup being present and that meant the styling was, with a lot of effort, replaceable, and if you wanted a pill component there was a pill component there waiting for you.

But Tailwind? Not the same, like, at all. Here, let me show you something:

(Click to embiggen. Had to filch one of them from the Wayback Machine, sorry)

One of these designs is made with hand-rolled, custom-written CSS. The other is made with Tailwind. Tell me again how you can instantly know which one is which? Because yes, that's right: I converted our site to Tailwind last year. It took me a few days of rote copy-pasting and there's some weird animation shit and article styling left in plain CSS (we're using Action Text so I target that with descendant selectors, not a great fit for Tailwind) but it all went well, and I reduced the amount of CSS shipped to end users by about 60kB. No change in fidelity, except where I'd made some opportunistic touch-ups and improvements as I went. Tailwind is far, far, lower-level than Bootstrap, to the point where you can go pixel-perfect with your UI designer if you want.

The defense rests

Now, if I swivel my ears just right I can already sense the reply-guys on Mastodon berating me for not being good enough with vanilla CSS. "Johan, you simpleton, you unmitigated clown, you shitfaced loser, if you were only a true blackbelt ninja like us real CRAFTSMEN you wouldn't have shipped a 73kB CSS file full of duplicate declarations to your end-users in the first place, do you even CSS???" is what I'm hearing.

Well. In lawyer circles, this is known as "the C defense." C programmers are a bristly bunch, known for their top-tier runtime performance and gratuitous buffer overflows. They'll gladly take the language's W for its runtime performance but they'll always leave the L for its buffer overflows to the developer. Why on earth would that moron let their null pointer get dereferenced, or their stack smashed? That wouldn't have happened if they knew what the hell they were doing. Not the fault of C at all. Case closed.

I'm not a fan of the C defense.

After some false starts my career began in an exclusively-frontend developer role for about a decade before I broadened my horizons and started covering more of the stack. I've worked with some real stars over the years, and I've still seen everyone get tangled up in append-only, change-at-your-own-peril 100kB+ suckfests when all is said and done. Tools that demand constant and delicate attention from everyone on the team to keep you on the happy path? That's how you get buffer overflows. I mean, CSS doesn't exactly push you into the pit of success. In fact it seems that after CSS has pushed you, you just kind of keep falling, hitting every specificity-shaped branch on the way down until you crack your head open on the !important rock waiting at the bottom of the cascade.

Re-use it or re-lose it

Even after reading a bunch of well- and not-so-well-reasoned articles I'm left with a sneaking suspicion that the biggest factor that determines whether you're a fan or not is the classname circumstance. If you get warm fuzzies from this:

<aside class="aside-container" />

Then this will probably give you intense feelings of "no thank you:"

<aside
  class="relative rounded bg-stone-50 text-slate-800 p-4 pt-8 md:p-8 drop-shadow-lg w-full sm:w-4/5 md:w-2/3"
/>

I get it! I really do, because that was my reaction too when I first encountered atomic CSS at SVT many moons ago. Ick! Blechh! But it's important to remember that functionality trumps aesthetics here. The all-encompassing issue with CSS, the poison pill you're always close to swallowing, is the problem of re-use and the fallout from your choices around that arena. Do you structure your re-use around class names and descendant selectors? That'll make it a lot easier to duplicate elements in your HTML, which is sometimes the correct tradeoff. It's what Bootstrap did, and that's how it gained its powers of standardization and theming. I can see how copying the aside-container in my example and putting it somewhere else is going to be easy with class-and-inheritance, and totally suck with Tailwind. You'll quickly pick up inconsistencies. Change it here, gotta change it there, too.

But here's the thing: Tailwind very much wants you to move your re-use up the stack.

Are you working with components? If so, you're only ever going to write that hideous relative rounded bg-stone-50... soup once. That alone should remove most of your objections. Then you re-use the component, not the class or the cascade. Your framework will probably allow you to mix it in somewhere else, or let components inherit from one another. Sky's the limit. You be the boss. And there are escape hatches, too: you can @apply classes (the documentation says not to, but I've had great success with extracting things like .input-field to a class because fields come in many shapes and sizes with many validations and they're often separate components) or add things to your theme config, or even hook into the CSS variables and write your own selectors with whatever you want in them. Each of these things will drop you down a level of abstraction, which typically comes at a maintenance cost, and that's why it's discouraged. Moving re-use to the component level is the first and most important piece of jiu-jitsu that makes Tailwind quick, efficient, and robust. The second part is co-location.

YOLO, COLO

There's still the holy trinity of HTML, CSS, and JS, in that order. Always has been, always will be. But React, for all its myriad flaws, showed us what it's like to move everything to one place where you can actually see it. It sounds like a small thing, but in practice it's incredibly powerful. And that's where Tailwind shines. If I continue my little aside-container adventure up there, what am I supposed to think when I see this?

<aside class="aside-container">
  <h2 class="medium-header margin-bottom">Cool</h2>
  <p>Lorem ipsum dolor sit amet!</p>
</aside>

Three elements! But I have no idea what any of them look like, not even the innocent-looking little p element. I'd have to inspect this in my browser before I could say anything at all about it, and even then I'd be missing a few pieces of the puzzle. Does the aside-container affect the rules of its descendants, via the cascade? Does margin-bottom behave differently depending on which class it's paired with? Is there default styling on the p or h2 element? Where are these things even defined? You'll often find default styling in one file, typography in one file, utility classes in one, and component styling in another. To properly assess this, you'd need to either go spelunking in your codebase or your documentation (lmao) before you know what's what.

In stark contrast, you have this:

<div
  class="relative rounded bg-stone-50 text-slate-800 p-4 pt-8 md:p-8 drop-shadow-lg w-full sm:w-4/5 md:w-2/3"
>
  <h2 class="text-xl md:text-2xl text-slate-900 text-center mb-2">Cool</h2>
  <p class="text-sm md:text-base text-slate-900">Lorem ipsum dolor sit amet!</p>
</div>

Sure, it's not nearly as neat. But at least you know what it does. No archaeology required. And any changes are done specifically here, where you can be completely certain they won't affect anything else in your app. Plus if you were to delete this component, its styling just... poof. Goes away. You ship exactly the classes you use, nothing more, nothing less, without ever having to worry about it. And I stress again that if you're using some flavor of component framework, you only have to write this shit once.

Bit carried away there

Look, I veered into proselytizing for a second which really wasn't my intention (they tell me Tailwind fans do this all the time, which might be the reason people hate it, terribly sorry, by the way did I tell you I also do CrossFit!!!) but I needed to demonstrate that some of us have thought deeply about these tradeoffs and still found Tailwind to be the superior choice over writing vanilla CSS in 2023. We do it because it confers certain advantages, not because we were lazy, or silly beginners duped by the all-singing never-ending Internet Popularity Contest. It's not right for everyone — nothing ever is — but it's a sound choice for veterans and beginners alike: stable, easily extended or bypassed, and easily migrated off of, should regret set in.

Do I sometimes need to write standard-ass CSS if I want some gnarly gradient or animation? Yes. Does that mean Tailwind is holding me back, or pushing me towards a mediocre middle? No! I wish we could talk more about the specific benefits of each approach rather than turn our noses up and divide people into Tiny Grasshoppers or Giga-Chad Developers based on how they respond to seeing many classes in their HTML. It's not that simple.