I'm Sorry Tailwind, Let's Be Friends

Tailwind is a very divisive topic with most people absolutely loving and swearing by it to people who passionately refuse to use it and scream it's faults from the rooftop. Let's talk about my journey from the latter to becoming the former.

Preface

All Tailwind example codes are taken from the official website or documentation located at tailwindcss.com.

What is Tailwind?

I would honestly be pretty surprised if anyone hasn't heard of Tailwind CSS by now! But for the uninitiated, Tailwind is a CSS framework that takes a utility-first approach to its naming conventions and usage. To contrast it against component based CSS frameworks, in Bootstrap, you might have something like this:

<div class="card">
  <div class="card-body">
    <h5 class="card-title">Card title</h5>
    <p class="card-text">Some content for the body of the card</p>
  </div>
</div>

Where each HTML element has no more than a few classes, sometimes one or less. The names of the classes represent the type of UI being styled. This starts off fine, but can lead to some long term maintainability issues I'll dive more into later. Spoiler: I was very stubborn and did not learn this lesson easily.

While the same general idea would be expressed in Tailwind more like this:

<div class="max-w-sm rounded overflow-hidden shadow-lg">
  <img class="w-full" src="/img/card-top.jpg" alt="Sunset in the mountains">
  <div class="px-6 py-4">
    <div class="font-bold text-xl mb-2">The Coldest Sunset</div>
    <p class="text-gray-700 text-base">
      Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptatibus quia, nulla! Maiores et perferendis eaque, exercitationem praesentium nihil.
    </p>
  </div>
  <div class="px-6 pt-4 pb-2">
    <span class="inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2">#photography</span>
    <span class="inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2">#travel</span>
    <span class="inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2">#winter</span>
  </div>
</div>

The short and sweet of it is that in Tailwind, you use classes that apply one specific property to your element. Reading over them, it can be reasonably assumed in most cases what that particular utility will do such as adding a predetermined amount of padding or setting a font size or color.

First Impressions: Alphabet Soup

I am not going to lie about it. I hated Tailwind. As I said in the opening, it can be a very divisive framework.

My first impression that began to build this conclusion was a very common complaint. Referencing the two code samples above, it's easy to be a little bit overwhelmed by the amount of classes in the Tailwind example, frequently called class or class name soup. I found it harder to read, harder to think about, and since I didn't know any of the classnames - slower to write.

This was a huge hurdle for me, and the one that always made me go back to styled components / emotion.

Changing Hearts and Minds

You are probably asking now, so what changed?

It came down to forcing myself to put aside my preconceived notions about Tailwind and how to write CSS in general. I came to the conclusion that Tailwind was gaining too much momentum to risk sleeping on and I forced myself to just start building with it and see the full lifecycle. This eventually led to Tailwind UI and building this site using one of their starter templates, by the way.

One realization at a time, I began to change my mental model and understand the power beneath the hood.

Realization #1 - The Apply Pattern and Thinking in Utility

One frequent retort to the complaint above is the @apply pattern which allows you to compose new utility classes using existing utility classes.

<!-- Before extracting a custom class -->
<button class="py-2 px-4 bg-blue-500 text-white font-semibold rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75">
  Save changes
</button>

<!-- After extracting a custom class -->
<button class="btn-primary">
  Save changes
</button>

The CSS that accompanies this:

  .btn-primary {
    @apply py-2 px-4 bg-blue-500 text-white font-semibold rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75;
  }

The documentation actually cautions against extracting out to such a style (semantic) and instead to extract utility classes; but I chose to include it because it's a perfect illustration of why I disregarded and misunderstood this feature.

The best description I can think of is that it made me feel like it was a completely unnecessary abstraction. Because I could only think in terms of classes named after their components or usage, it felt like writing CSS through the proxy of utility classes and then my markup being essentially the same. Why not cut out the middle man?

When you use the pattern instead for a more complex utility, the simple elegance becomes extremely more clear. For example:

.flex-full-center {
	@apply flex justify-center items-center
}

As an experiment, I pulled up the last major project I released and did a quick scan to see how many instances of those three particular styles appeared and I found 97 different styled components with those three styles. That's 6KB of CSS before GZIP just for centering things. This is one of the biggest drawbacks of semantic class naming and organization.

Where this pattern shines is that middle ground where you have repeated yourself so many times that extraction makes sense, but extracting a stand-alone component would be overkill.

Realization 2: Shipping Less CSS

I won't sit here and lie and say I had reached the apex of shipping only necessary CSS, but I thought I had surely gone to the big leagues at least. All of my projects had a very small global stylesheet that was more or less used on every page. Then everything else was styled components that lived alongside the React components so that they could be chunked into much smaller packages of what I needed page by page.

I was blown away once I began to see how much smaller my CSS payloads were with Tailwind. The framework in its entirety starts off extremely large, even more so once you begin generating custom utilities, adding colors, and things of that nature. When you build your production bundles, a combination of Post-CSS and CSS Purge removes any of the styles that are not being used in your application, leaving only the bare necessities.

I know some people can be a little shy about adding yet another build step to their application, but the setup is painless and fast. I'm not one to advocate for obsessing over bundle size, especially since one poorly optimized image can easily be several magnitudes larger than your entire site's CSS. But it's a sizable benefit you get for free without really having to ever think about it or spend time on it, which is a win.

Realization #3: Never Build a Theming System Again

This was actually the most recent realization as I was building the website you are looking at now. I knew Tailwind was customizable from a brief glance at the documentation, but it wasn't until using this TailwindUI starter that I realized exactly how powerful the built-in theme system is.

Show of hands, who has built out a theme system from scratch?

It's never a ton of fun, at least not for me, and the more that I dug into Tailwind's system of defaults, overrides, presets, plugins, and utility generation, the more I realized I have about 60 hours in every project I've built in recent years that covered functionality Tailwind would give me for free. And to sweeten the pot, instead of the system being designed by just me, it's the collective work of hundreds of contributors.

Fun example, Tailwind's color system is based off of a series of available color groups like slate, rose, or zinc and shades ranging from 100 to 900. You can override these or create entirely new color sets and it automatically generates the necessary classes for you with no extra work. Then just as described above, your final CSS bundle will only contain the classes that you actually use so your users aren't paying for colors you thought you might need and didn't use.

Realization #4: Consistency

Consistency has always been my achilles heel. The small differences in details like margin, padding, font-size, and spacing can start off subtle bordering on invisible, but as they congregate, they can quickly lead you to a layout that just looks disorganized and strung together.

Tailwind is not a silver bullet in regards to this problem, but having predetermined sizes for most properties that I can base my design off of and customize very easily both during development and during revisions is the easiest way I've found so far to attack this.

This may seem like an trivial problem not worth mentioning, "just use some CSS vars, Jody!" But then we end up back at realization #3. Now I'm working on a theme system again instead of the fun features that I want to be working on.

Realization #5: Time Savings in Customization

I've always had a love-hate relationship with component based CSS frameworks or React UI kits like Bootstrap or Chakra-UI. Because on one hand, it's nice to be able to immediately start laying down UI and building features instead of boilerplate and "back office" code. But the majority of my work is either for clients at my day-job at an agency, or freelance clients for my own agency, so there are agreed upon design parameters that I don't dictate. Also, just between you and I, I can't even design a bake sale flier. A good designer is such a treasure.

So what I've come to learn through much trial and error is that I almost always spend more time massaging these components to match the design I am given than it would have just taken me to create them myself.

With Tailwind, I can immediately start building exactly what I need and only what I need, cutting straight to the chase and this saves me time and as such saves my clients money. To address the elephant in the room -- yes, there are always exceptions. I have a literal negative amount of interest in building a from-scratch DatePicker for example. But for those extremely complex components, there are no shortage of fantastic headless UI kits like HeadlessUI by Tailwind Labs that do the heavy lifting.

Some Gripes and Trade-Offs

There is no such thing as a perfect solution, and while Tailwind has become my weapon of choice, there are a few gripes and trade-offs that I feel pertinent to mention.

Is it "easier"?

A common claim made in social media circles is that Tailwind is easier than writing your own CSS, especially for beginners. There is some truth to this in an architectural sense that I am going to mention shortly -- but in terms of "I need to build this design", I disagree.

The reason being is that in order to be productive with Tailwind, you need to already have a strong grasp on CSS. This is because in a utility based approach, you are applying atomic properties, not complete component styles. If you need to build a card, you can't just use class="card" and be off to the horse races, you need to be aware of each CSS property involved and how to compose them for the effect you need.

CSS Architecture

I've actually had this debate with Adam himself 😬, and I did not articulate myself properly so hopefully I can find better words this go round.

Most of the complexity in CSS, in my opinion, is not "How do I make this box look like I want it to", but rather in the architecture. Details like shipping the minimal amount of CSS required, understanding the cascade and specificity behavior so you are not fighting with yourself to make changes or overrides, or ensuring the long term maintainability of your styles are handled for you in Tailwind. This is a double edged sword.

On one hand, it's a powerful time saver that people should absolutely take advantage of. On the other hand, what if you find yourself unable to use Tailwind? My projects at work exclusively use CSS-in-JS solutions with component UI kits. I maintain that if you still take the time to learn these skills, it will make you more effective at CSS in all your projects whether they be Tailwind or something else.

Accessibility

Not all UI kits / CSS frameworks are created equally in this regard. Bootstrap as an example is not oblivious to the concerns of accessibility, but does not come anywhere close to the amount of work and care put into projects such as Shoelace Styles or Reach UI.

But in any case, when stepping back into what is essentially a "vanilla CSS" approach at the end of the day, by that I mean no pre-build components, you lose any of the accessibility work that has been done by the library maintainers and contributors and you need to be more aware of additional steps you may need to take to ensure the accessibility of your application.

If you are using HeadlessUI or other similar libraries in combination with Tailwind, the sting of this potential issue will be far less problematic, but still something to be aware of.

Conclusion

I started off as the most intense of skeptics, very vocal and confrontational about my feelings around Tailwind. But now I have no intentions of using anything else for my personal projects and freelance work and am pushing to convince coworkers to try their own experiments to assess what us adopting Tailwind as our default may mean and what trade-offs that would involve.

Sometimes it's good to remind yourself that you can't be sure how you feel about something without giving it a thorough and good-faith try, and if you find yourself relating more to my initial thoughts and less with my realizations, then maybe you just need to build something top-to-bottom with Tailwind and experience the full lifecycle of benefits?

Or maybe you already have and just don't like it, that's totally fine too :).