If you work in the front-end, you've probably heard a lot about TailwindCSS, a CSS library, much like Bootstrap. Much unlike Bootstrap, however, Tailwind takes a different approach - it is all "utility classes".
And I am not a fan. I got a whiff of it and quickly learned the name is appropos: it was as welcome and useful as passed gas.
Before we start, let me try to explain what a utility class is. Let's say that you have many components, and many of them need to have the CSS style property: "display: flex;". Instead of writing that over and over in your css, multiple times, instead you create a class called "flex"
.flex {
display: flex;
}
Then, in every component that needs to be flexed, you add that "flex" class.
This is not a bad thing. I have written, and used utility classes a great deal myself, especially when I'm writing CSS without the aid of CSS-in-JS solutions or a preprocessor like Sass/SCSS.
What Tailwind does is take that concept to the extreme, with the idea being that you almost never have to write CSS, you just write different classes based on what styles you need to apply.
Which is an interesting choice, because...
This is just inline styles with extra steps.
That's it. Writing <div class="flex">foo</div>
has the same exact effect as writing <div style="display: flex;">foo</div>
. Well -- slightly different in that inline styles have higher priority than classes, but that's not really relevant in this context.
So - with that in mind, with the exception of CSS prioritization, any argument you could make against using inline styles in your codebase is also an argument against using Tailwind. For example: Lifewire: Avoiding Inline Styles for CSS Design. Or StackOverflow: What's so bad about inline CSS?. Or LogRocket: Why you shouldn’t use inline styling in production React apps.
I know it seems a bit lazy to rehash other users criticisms of inline styles to explain what's wrong with Tailwind, but it really is a 1-to-1 mapping. It's just inline styles with extra steps.
Some of the problems Tailwind shares with inline styles:
It's WET, not DRY.
When you want to change your site styling in a major way, if you've used utility classes, you need to go through each use of those utility classes - that is, every component - and visually determine what needs to be updated. For example, let's say that your company's primary color is blue. You'll have lots of blue stuff in your website, marked with things like: "text-blue-500" or "bg-blue-300" to determine different shades of blue. And that's fine until your company decides to rebrand, and all of the buttons - but only the buttons - on the site need to be red.
Now you have to go through each component and manually change "text-blue-500" to "text-red-500". And with 1000 edits comes 1000 oppertunities to introduce a bug. It is almost a textbook definition of why the DRY principle is in place.
Alternatively, if you're using regular-old CSS, what you probably did is create a class called ".button". You can just go into that class and change a single line: "background-color: 'red';". Any element that uses that class definition will now be red.
That brings us to the next point:
HTML should only concern itself with the structure of your page, not the styling of the page.
People talk about seperation of concerns a lot in development. CSS Modules (and especially .vue files) have done a lot to dispel the notion that you need to segregate structure, behavior, and style of the same basic building block of your site in seperate folders, but there is something to be said for seperating concerns. That is - each part of your code should be "loosely coupled and highly cohesive."
In other words, your HTML (structure syntax) shouldn't have information about what the styles should be, it should only contain information about the structure of the page.
Indeed, the ultimate reason for the invention of CSS, the whole point of the entire enterprise of CSS... was specifically so that you could seperate content from presentation.
And the method for doing this is through the "class" attribute.
The whole point of "class" is specifically that you can tell the computer what an element is - that is, describe an element's content. Once you've defined the content, then you just need to decide what content of that type should look like.
This not only means that you can go and change how an element looks without worrying about the underlying structure of the page, but also means that you can use these classes to describe what an element is. Indeed, part of the reason for BEM's naming syntax is that BEM names not only tell you what the component is, but also what it's relationship to other components in the document is.
Remember that when we write code, we write it for two audiences: the first is the computer itself, which doesn't care how the code looks so long as it runs, and the other is your fellow programmers. The easier it is for them to quickly identify what parts of your program are and how they interrelate, the more quickly that they can fix bugs, add features, and bring value to the organization.
Which brings us to:
It's hard to read
If you look at some HTML with Tailwind in it, you might say to yourself that the HTML looks "busy" or even "ugly." That's true, but it's missing the point.
Say what you will about inline styles, but they're at least providing enough context to let you know what's happening. Tailwind code is full of semantically obscure abbreviations; most of which are just redefinitions of already well known CSS properties.
Worse still, when they're not redefinitions, they can become downright cryptic. Tailwind prefers to use prefixed class names instead of media queries. Here's an example from Aleksandr Hovhannisyan
So this in Tailwind:
<div
class="w-16 h-16 rounded text-white bg-black py-1 px-2 m-1 text-sm md:w-32 md:h-32 md:rounded-md md:text-base lg:w-48 lg:h-48 lg:rounded-lg lg:text-lg"
>
Yikes.
</div>
could be expressed as:
<style>
.thing {
width: 16px;
height: 16px;
color: white;
background-color: black;
padding: 0.25rem 0.5rem;
margin: 0.25rem;
border-radius: 0.25rem;
font-size: 0.875rem;
line-height: 1.25rem;
}
@media screen and (min-width: 768px) {
.thing {
width: 32px;
height: 32px;
border-radius: 0.375rem;
font-size: 1rem;
line-height: 1.5rem;
}
}
@media screen and (min-width: 1024px) {
.thing {
width: 48px;
height: 48px;
border-radius: 0.5rem;
font-size: 1.125rem;
line-height: 1.75rem;
}
}
</style>
<div class="thing">Yikes.</div>
Now, the first example, I grant, is an awful lot less code to write, but look at how the second example is explictly defining height and width at specific breakpoints.
It is verbose - as raw CSS usually happens to be, but there are other solutions - such as Sass/SCSS, or solutions such as Emotion, Styled Components, etc. which allow you to use much more terse syntax without losing the cohesive meaning behind it.
Again, this is programmer 101. It's why senior developers get on junior developers for naming variables "const h = 10" instead of "const height = 10"
Another reason why the latter is easier to read than the former - Tailwind's classes are arranged horizontally, while the CSS is written vertically. The wider text is, the harder it is for a reader's eyes to jump to the next line, and the harder it is to find the one particular word you're looking for in a wall of horizontal text.
I bet your eyes started glazing over the second you saw the horizontal scroll bar on that Tailwind code sample, didn't they?
You lose a lot of the features built into standard CSS
I won't harp on this too much, but it should be pointed out that because Tailwind doesn't allow you to use the power of many of CSS's basic features. You can't chain selectors together, like so:
.foo:focus,
.foo:active,
.foo:hover {
/* css code */
}
You can't use combinators.
.foo p {
/* all p that are decendants of a .foo */
}
.foo > p {
/* all p that are direct children of a .foo */
}
.foo + p {
/* all p that are directly -after- a .foo */
}
.foo ~ p {
/* all p that are siblings of a .foo */
}
It solves a problem that doesn't exist.
One of the craziest things is that there's an obvious limitation to Tailwind's utility-class paradigm. What happens if you want to group related styles together? Rarely is "display:flex;" used without "justify-content: {value}", for example. CSS allows you to group these styles together into (wait for it), classes.
There's a tool for grouping related Tailwind classes together too. It's called @apply. It's special, non-standard syntax that goes in your CSS file (a directive) and allows you to string together a collection of tailwind classes and place them all under one class name.
That is to say, completely defeating the purpose behind the utility-class paradigm. If you end up having to use @apply, then *why don't you just use normal, ordinary, conventional CSS, which is easier to read, understand, modify, and doesn't require special tooling or parsing. CSS syntax can be complex, but it's been pretty stable since the late 90s, and isn't going to radically change anytime soon.
There's a very simple mental experiment I'd like to conduct with you.
Imagine a world in which CSS was never developed, but something akin to Tailwind was. That is, webpages could only be styled through repeating these individual class names... presumably through using table tags for layout. (To give you an idea of how old I am, I used to code web pages as a summer job in my junior year of high school in 1996 - and we used a LOT of table tags.)
If you could go from the limitations of Tailwind to CSS, wouldn't you consider that a quantum leap forward? Expressive syntax! Semantic naming! Style grouping! Selectors and combinators!. It would be like moving from Assembly to C for the first time. If so, why are we considering replacing CSS with something that does less, is more complex, creates bad quality codebases, and possibly ends up with massive vendor-lock in down the line?
If you want better than CSS, there are already solutions.
So a lot of the hype around Tailwind is that you can get rid of CSS. I know, everyone knows CSS can be hard to work with - especially if you have legacy codebases where the CSS wasn't written that well.
But for the most part, there are other, better improvements on CSS that actually do make styling simpler. There's the various CSS-in-JS solutions that allow you to leverage the power of Javascript to create dynamic class definitions; there's preprocessers such as Sass/SCSS/LESS; there's linters like Stylelint; there's best-practices methods like BEM/SMACSS. Is there overhead in learning these technologies? Yes. Is there tooling that needs to be part of your build chain? Yes. But unlike Tailwind, all of these solutions actively provide a tangible benefit to your code -- which is something that Tailwind can't claim.
It literally provides no value, and tons of problems.
At the end of the day, what do you get for all these problems? What are you left with? You're basically left with a less readable, more complex version of inline styles, a coding technique that we've been trying to breed out of junior developers for the past decade or so.
If you adopt Tailwind, it's going to provide problems for you and your team for years to come, and it's going to be hard to remove it.
Updates based on the comments section.
A few notes based on responses from the comments section.
Why trash something if you don't like it?
It's important to write about bad frameworks as much as it is to write about good ones, because of two reasons.
First, is the John Stewart Mill argument of "the value of the wrongful idea" - that in making a (good faith) argument for something incorrect, one arrives at a more correct, more complete view by analysis and refutation. Ideas must be continually challenged lest they go stale. Indeed - "one who doesn't understand one's opponent's arguments does not understand one's own" is a maxim I try to apply. When I wrote this article, I tried to look for the good in Tailwind. Why do people like it? (They don't have to write css. They can put style info in their HTML. They can write terser code. It gives them power to do things they don't know how to do in css.) Once I knew why people liked it, I had a much better understanding of why I didn't. (It combines content and presentation. It makes things harder to maintain. The syntax is obscure. You lose the power to do things that you can do in css.)
Second is that someone down the line is going to think: Hmm, should I add Tailwind to my app that has to be maintained by my team? And they're going to google "pros and cons of TailwindCSS". There will be plenty of articles explaining the pros. Here's one explaining the cons. Hopefully I've made a compelling argument not to use Tailwind so that future developers won't have to deal with it.
You're being disrespectful to the people who like Tailwind.
This isn't New Orleans Jazz.
I don't like New Orleans Jazz, so I don't have to listen to it. I don't buy New Orleans Jazz albums.
I am not in the habit of making detailed criticisms of what I feel to be the music compositional problems of New Orleans Jazz.
But I have never had a team lead, product owner, or stakeholder come up to me and say: "For the next project, I'm thinking that everyone on the team has to learn how to appreciate and play New Orleans Jazz."
Engineers and developers are often required to work with technology that they not only don't like, but which makes their work harder - often because decision makers either didn't care about the software's tradeoffs, or didn't know. Can't do much about the former, but we can do things about the latter.
When team leaders are thinking about incorporating a new technology into their tech stack, they should look for blog posts like this one to help them evaluate whether or not it's worth a try.
My thesis is not, as you seem to think, "I don't like Tailwind, and therefore YOU shouldn't like Tailwind either". That's a 12 year old's viewpoint of technology criticism.
Rather my thesis is: "If you choose Tailwind for a mission critical application, you will end up making your job harder, your application more brittle, and your team, in the long-term, will suffer."
But CSS has massive problems!
It really does. And there are better solutions than plain CSS. But Tailwind isn't one of them.
Say that in the 1990s, the only way to build a house was to bang nails in with a flat rock (CSS). And then, around the mid 2000s, a really smart guy invented "the hammer." (SCSS) It took adjusting, and you have to learn a new tool, but it did the job much better.
Around the early to mid 2010s, another guy invented the nail gun (CSS-in-JS). It did a lot of the same stuff as a hammer, but you had to know how to use it. There were tradeoffs, but generally, people who chose to work with hammers or with nail-guns usually ended up okay. Many peoplee would often use a manual hammer when the manual hammer seemed appropriate, and the nail gun when they seemed to need it. And all was good in the world of carpentry.
Then in 2017, someone came up to the carpenters and said: "Hey, see what happens when I do this!" and starts hammering in nails with the butt end of a loaded revolver (Tailwind).
And it's supporters quickly point out how more effective it is at building houses than banging in rocks.
"But it's a loaded gun. It might go off and shoot someone"
"Hasn't happened to me yet."
"Why don't you use a hammer? Or a nail gun?"
"I don't like hammers or nail guns."
"I can understand why you might not, but even if you used a rock, that would be safer in the long run."
"But using a rock is so difficult and inefficient."
"I'm not saying to use a rock. I'm saying that the hammer already solves the problems you have with the rock, but even the rock is a better tool for this than a loaded gun because it won't shoot anyone."
"But I love my gun."
"I guess it's alright if you use your gun on smaller projects on your own property, but..."
"Nope, I'm the foreman. Everyone on the site is using loaded guns from now on, because they're awesome."
Update: 9 May 2021 - Check out this blog post by Mykolas Mankevicius which attempts to rebut this article. I disagree, of course, but I think it adds to the debate, and if you're reading this deciding whether to use tailwind or not, you should hear what the "other side" of this issue has to say.
Agree but think my writing style might be too abrasive? Check out Benoît Rouleau's take on this article entitled Tailwind CSS might not be for you
Cher writes about some of the response this article has gotten and how it relates to our own unconcious bias in "Sexism, Racism, Toxic Positivity, and TailwindCSS"
Interesting article, a different view about the css "star" of this moment.
However, let me do briefly the devil's advocate.
For example, to avoid classes pollution and the "style inline" effect you can use the @apply directive.
tailwindcss.com/docs/functions-and...
So you can leverage the simplicity of tailwind without polluting your code.