Why I don't use Tailwind CSS in Production
Tags: css, tailwindcss, webdev
Published: 2022-03-11 21:36:15

_This is part two of my two-part analysis of Tailwind CSS – [link to part one](https://blog.shimin.io/whats-the-big-deal-with-tailwind-css-anway/)._

Last week's post covered Tailwind CSS's greatest strengths: 

- Good off-the-shelf yet configurable design system. 

- Increases team velocity by flattening specificity. 

- Provides good guardrails for developer/designer handoff.  

All tooling decisions have inherent tradeoffs. Use Java over C and you lose fine-grained memory management. Choose Python over Java and you lose compile-time type checking.
   
While I love Tailwind for prototyping, I do not use it for production codebases. Where maintainability is key and there's time to document and enforce style workflow.

## Cost of Abstractions

A popular mental model of Tailwind is as a set of CSS shorthand with corresponding Design System tokens. So you need to know CSS well to use it effectively. 

But if you need to know CSS to get the most out of Tailwind, why memorize a new set of class names?

For example, Tailwind's default `basis-6` class translated to `1.5rem`, but that's about as fun to memorize as the multiplication table. On top of having an open MDN tab, now I need a Tailwind tab too. 

Tailwind forces an extra HTML-based abstraction layer for a CSS job. This is always going to be an anti-pattern. On balance, I think it's slightly worse than just using inline CSS – either via Styled Components or "vanilla CSS". At least inline CSS doesn't force me to memorize a bunch of new class names. 

If you read the last sentence and thought 'but inline CSS can't handle media queries or pseudo selectors!', you can achieve both via CSS custom properties. Check out this demo:

{% codepen https://codepen.io/shimin-zhang/pen/yLPwGRZ %}

## The "Ugly" Class Names Problem

Here's something that the Tailwind documentation and I can both agree on: the long string of Tailwind class names are ugly. Here's a quote directly from the [official docs](https://tailwindcss.com/docs/utility-first):

> Now I know what you’re thinking, “this is an atrocity, what a horrible mess!” and you’re right, it’s kind of ugly. 

'Ugly' and 'horrible mess' implies the classes are hard to comprehend. Hard to comprehend things are impossible to maintain. I also want to point out that the sentence does not end with 'but it becomes so much easier to follow once you are used to it!`. It only becomes marginally better with familiarity.

Tailwind _does_ help you _create faster_ by cutting out time spent writing descriptive class names and worrying about specificity. But class names with methodologies like [BEM](http://getbem.com/) also provide semantic meaning and create (hopefully useful) layers of abstraction.
 
Bad abstractions lead to unmanageable CSS stylesheets, yet the answer need not be '_remove all abstractions_'. 

![Remove all style abstractions](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fcqsf8whfckap95d2kut.jpg)

Let's take a look at the user experience of a CSS update in 4 scenarios. Let's also pretend we are a new dev trying to double the block direction (y-axis) margin of the green block in viewports > 1536px.

{% codepen https://codepen.io/shimin-zhang/pen/mdqgwZy %}

Our first variation – the stylesheets in the attic corner approach. Here, CSS declarations are set in arbitrary class selectors and it's impossible to know where our new margin should go. It should go in a new `.hero` block under the large `@media block, though the whole thing should be refactored because it hurts to read.

{% codepen https://codepen.io/shimin-zhang/pen/QWOPgQK %}

Here's the Tailwind variation. It's definitely easier to read, though I still did a lot of scanning to find the `my-12` y-axis margin class in order to figure out where to put the new `2xl:mt-24` class. 

{% codepen https://codepen.io/shimin-zhang/pen/MWOdYvX %}

Our third example: a CSS module-like approach where there are no risks of clashing selectors. We have the same reading pattern as scenario 2. I scan the entire file and find the default top margin value, then double it in the large viewport block. 

I find the UX between scenarios 2 and 3 fairly similar, we make a minor tradeoff between file size and developer time (Tailwind is smaller, but we need to keep the tab open to find out `my-12` is `3rem` in size). 

{% codepen https://codepen.io/shimin-zhang/pen/VwrOYyY %}

Our organized CSS scenario, where the class names are abstracted via a simple convention: 

- `container-*` classes to handle container-based properties such as padding, width, border-radius.

- `layout-*` classes to handle the element's relationship with others, such as margin, align-self, and float. 

- `*` catchall for everything else. 

In this case, we change the margin by changing `.layout-centered`'s style, perhaps splitting it into another layout class that has twice the y-margin for larger viewports. 

This is especially useful as our styles grow more complex. Or when you read another developer's code. With Tailwind, you are forever stuck with a linear reading of all class names. With good abstractions, we can [chunk](https://en.wikipedia.org/wiki/Chunking_(psychology)) and navigate in a fraction of the time.
  
Messy HTML isn't just aesthetics. It's technical debt that will make your styles harder to read and maintain. 

## What about using @apply? 

Tailwind provides a solution to the 'lack of abstraction' problem, the [@apply directive](https://tailwindcss.com/docs/functions-and-directives#apply). You can use it to mix existing Tailwind styles and create new class-based abstractions. We can for example create our layout abstraction from the above scenario 4:

```css
.layout-centered {
  @apply my-12 mx-auto;
}
```
But `@apply` has two sins:

- It's just regular CSS with another layer of abstraction – already covered above.

- It makes your code even harder to read. Before @apply you have a single source of truth. After @apply, style can be coming from a number of classes.

The second problem is especially troublesome, it easily leads to specificity issues – the reason why @apply has [special treatment](https://tailwindcss.com/docs/functions-and-directives#apply) for `!important`. 

Don't take my word for it, this is what Tailwind's creator says about `@apply`:

![Adam Wathan Tweet](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jld7fnjsga6md8fkbjgv.png)

## So What's the Point?
I am not writing this post to bash Tailwind CSS. I like [most of its features](https://blog.shimin.io/whats-the-big-deal-with-tailwind-css-anway/) and believe component-based style reuse is the future – at least in a front-end framework context. I also have reservations about using it in a large codebase. 

If you are on the fence about Tailwind, I hope my analysis of its strengths and weaknesses can help you make better decisions. 

And if you are thinking about creating the next best CSS framework, this post is an additional data point on what Tailwind CSS does well – and how it can be improved.