I see Tailwind being used by a lot of people. I totally get why it’s so popular. It looks great, the design choices are sensible, and you have copy-pastable code that reliably renders in the same way.
It nets you results quickly which feels nice and rewarding. I think Tailwind is a perfect framework for two kinds of situations:
- people who don’t want to learn CSS
- people who want a standardized look and want to copy paste from an external library (i.e. Tailwind UI)
I mostly wanted to shut up about Tailwind, and I hoped that people would realize it should be avoided after a while. But that doesn’t seem to be happening. So I think I have to fight the fight and talk about why you should not use Tailwind.
I have had a lot of thoughts about Tailwind, and most of these thoughts come from a place of being the code maintainer of a large scale web application codebase. So mind you, when I am talking about regret in the title, I am talking about a big web app context where your code is going to be maintained for years to come.
I am not talking about your Sunday side project. I am talking about real, big work® projects here. I am talking about maintaining something that has a lot of complexity that many people will work on for years to come.
You might be tasked with choosing a CSS architecture for that. And you might have heard about this framework called Tailwind. It’s popular right now and it seems to be the 2020 choice.
You look at the website and you see this nice framework that promises you that things are going to be cool in Tailwind land. And you might be thinking: cool, let’s try that.
I urge you to reconsider your options. I would advise you to not use it.
I really don’t know why Tailwind got so popular over the last year. I think it’s because it does a lot of things right. Surely if the authors can make $1.8M in preorders for Tailwind UI, something must be up. But at a fundamental level, I think its ideas are wrong and lead to problems, and I am going to explain why.
The main problem is that the kind of code you are creating with Tailwind, especially on large scale applications, is the kind of code that you’re going to have to swap out in a few years. Why? Because it’s unmaintainable at best. I will explain below.
An API on top of an API
Tailwind is a framework that heavily relies on utility classes. Utility classes are classes that typically do one single thing e.g.
.flex makes a
display: flex. Utility classes create an API on top of what is already a declarative API (CSS itself).
This can lead to problems when
- the original API (CSS itself) might change in the process, so now you have a mapping problem
- the mapping itself is not that great, and has bugs on its own
To dive into situation 1, let’s think about how maybe CSS grid will have an evolution in its syntax. Now you don’t just have to update your code, but also the in-between framework (Tailwind).
To dive into an example of situation 2: I was on a project a while ago where somebody mapped Flexbox to utility classes wrongly (i.e. the mapping contained obvious mistakes) and that code had made into production, was versioned, and we couldn’t change it. I thought that was very painful.
Tailwind’s coding style creates its own problems, which you then have to solve
There’s also a deeper problem: because you are essentially recreating the CSS API with utility classes, you end up with a huge file. The current Tailwind utilities.css file is 1.8 megabyte and contains 80 000 lines of code.
Tailwind proponents will tell you that you can remove unused CSS with Tailwind’s tooling. And this is exactly the problem that I want to talk about: now you have to solve a problem that’s inherent to the framework, adding complexity to your project.
In the Routify and Svelte Discord channels I routinely see people struggling to get the toolchain around Tailwind working. So many people struggling with a toolchain that is totally unnecessary is a real problem. Removing code that shouldn’t be there in the first place should not be necessary.
Sidenote: I’m not against utility classes
Surely it feels nice to be able to quickly write flexbox things in a declarative way, and it has it’s uses. I’ve happily wrote declarative flexbox code in the past to quickly set up layouts. I am not against utility classes at all. It’s quite handy to set up layouts quickly with utility classes. I can also totally relate to the idea of designing in the browser with a set of utility classes.
When I do web app layouts we typically have a lot of different panes and panels going on in your app with varied logic about how they should resize. In a prototyping or an early app phase it’s super handy to not have to write a specific (BEM) component for everything and just build things out with utility classes.
But you have to be careful with utility classes. Because what you’re essentially doing is building a complication on top of CSS itself.
When you use a utility classes, for example declaring a flex utility on a div, you are doing it in HTML instead of declaring it in CSS this can quickly lead to unmaintainable code.
I use utility classes in one-off situations. Tailwind style code uses utility classes for everything. Tailwind lovers will now come back to me with arguments about
@apply but this is just throwing more complexity on something that is already complex.
Adding complexity to complexity
Tailwind’s HTML code is fine for very simple projects (which will now have the drag of a whole toolchain to purge unused selectors but OK).
But let’s just imagine you have a bit of a bigger project, with a basic app layout that is working, with a few panels and a few buttons.
You now have to update the code for the next requirement – a bit of responsiveness. The UI has to change according to a breakpoint. So you add some Tailwind breakpoint classes to the thing you are making.
You realize your specific situation requires a very specific breakpoint. So you change your Tailwind config to add this breakpoint. You kind of realize it would be easier to just write the breakpoint directly in CSS, but you want to do things the Tailwind way.
If you would do things the regular CSS way, your media queries list basically lives in two worlds: the tailwind config and a local CSS file. And now you’ve lost your breakpoint structure. So you have to adhere to the Tailwind style.
And then let’s say you have a new situation: dark mode has to be supported. So suddenly your colors all have to change according to the dark mode logic. So now you add a bunch of classes to whatever you are creating again… you look at your button code and you see that the color of the button is directly in the HTML (
gray-100). And the opposed color is actually called
gray-900 in your design system. So now you add a class to the button for the dark situations. And you now realize that there are 7 variants of your button in your codebase and they all have different class combinations.
Now, with the previous responsive example, and this dark thing piled on top of it, you have a sorry mess.
And then you realize that some aspects of what you’re doing would be way easier to handle directly in (S)CSS, using contextual media queries and proper use of the cascade.
Maybe your web app is never going to see the complexity I am talking about. Maybe your apps are not responsive or they don’t support dark mode. But most of my work supports some form of skinnability, is as responsive as possible and needs to be maintained over several yerars.
Putting values on the same class level in HTML is basically destroying the cascade for you. So don’t do it.
Complexity overload and no proper way to manage it
The problem with Tailwind is that you end up with an overload of classes in your HTML and no way to manage these.
Tailwind’s creator says it’s not a problem because your code can essentially lives in deep presentational components and it can be abstracted away. But this kind of coding strategy leads to its own problems (e.g. prop drilling)
I’m especially wary of this situation where you have one button that has 6 HTML classes, and then another button that has 5 HTML classes, and another one that has 7 HTML classes. And they’re all different combinations. Now you have to do some serious digging through the codebase to try and bring everything in line, and you’re going to be comparing which of the three buttons have class overlap. Nobody is waiting for that task. And frankly, it’s probably just not going to happen.
It’s code rot that’s going to happen as soon as you have a few people working on a project with moderate complexity.
I fear for a Tailwind future
I fear for the general codebases of web apps when too many people decide that Tailwind is the way to go. Now, on the other hand, this means I’ll have an infinite stream of work to refactor bad codebases to better ones. So I guess I’m not complaining.
Just do yourself a favor and stop using Tailwind.