Tailwind is not worth it
TailwindCSS is the first UI framework that really clicked for me with more than good enough defaults. It enabled me to quickly prototype a UI someone with actual design skill can refine. And thanks to its widespread use LLMs are fairly well versed in it as well. But looking past the convenience I now believe using Tailwind is not worth the risk.
While I try to blog a lot more without adding fifty disclaimers because people on the Internet want to misunderstand a post, there are a few I feel are important on this one. I will elaborate my stance and reasons for it. Feel free to agree, disagree or pick and choose pieces of it. This post is not meant as an absolute truth nor as a call to action to do what I am doing.
Supply Chain
Sadly the words "supply chain risk" have become more and more known and repeatedly heard across the Internet over the last few years. I am saying sadly not because the risk exists, it always did. But while it existed, the impact and the number of issues changed.
JavaScript and npm are a constant source for compromised packages. Just in September 500+ packages were compromised. This is not the first time and it surely is not just a JavaScript problem. In my opinion the issues is more pronounced for JavaScript as the general culture seems to be to use as many packages as possible to not type out three lines of code. The most prominent example is likely left-pad.
I think we can all agree on the general rule of "the more third party packages you use, the higher the risk to fall victim of a supply chain attack". Technically we are all vendoring dependencies and reviewing the code we include in our projects, right? While this is actually happening in some industries, it is not the standard nor will it ever be.
Tailwind
Let us assume you do not want to include all of Tailwind in your project. Considering Tailwind actively discourages this should be indicator enough that it is not a good idea. The most minimal setup seems to be installing Tailwind CLI.
npm install tailwindcss @tailwindcss/cli
Now this obviously will download some packages.
timo@dev:~/tmp/twnpm$ ls node_modules/ | wc -l
30
And we have such important packages like is-number
on our system. Because we could hardly be bothered to write three wee lines of code or copy and paste them.
timo@dev:~/tmp/twnpm/node_modules/is-number$ cat index.js
/*!
* is-number <https://github.com/jonschlinkert/is-number>
*
* Copyright (c) 2014-present, Jon Schlinkert.
* Released under the MIT License.
*/
'use strict';
module.exports = function(num) {
if (typeof num === 'number') {
return num - num === 0;
}
if (typeof num === 'string' && num.trim() !== '') {
return Number.isFinite ? Number.isFinite(+num) : isFinite(+num);
}
return false;
};
I want to acknowledge that this sounds very much like me hating on JavaScript and / or Tailwind. But this is the wrong takeaway. I merely use them as an example, you can do the exact same, bad thing in other languages just fine. If I would look around a bit I would take a bet I can find equally "complex" packages in Python, Ruby or Go.
For what?
Now what exactly is Tailwind’s job? It provides classes you can add to your HTML to style them. This gives consistency and an easy way to change things globally, like the primary color or the padding of a paragraph. Except there is a bit more to it.
Let us assume you add padding all over the place using px-5
but then your designer decides buttons should only have a px-4
. You can search your code for all occurrences and change five to four. Or you planned ahead and used a custom variable for buttons. Or you globally change what -5
means and hope your designer does not notice other padding changed as well.
So what is more likely is that you add some utilities around Tailwind to make changes like this easier.
Now to the second thing Tailwind does. With its CLI you can scan all your templates and create a CSS file to ship which only includes the definitions you actually use. This greatly reduces the size of CSS you have to deliver to your clients. Smaller, faster, easier to parse - you want to do this if you are using Tailwind.
Template system
I am currently building an app in Go using gomponents. This setup is pretty amazing and it makes it easy to show how to abstract a "button". Again, this is not Go specific, but works in nearly all other languages with similar template engines.
const (
buttonBase = "rounded-md px-3 py-2 text-sm font-semibold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2"
buttonPrimary = "bg-indigo-600 text-white hover:bg-indigo-500 focus-visible:outline-indigo-600"
buttonDanger = "bg-red-600 text-white hover:bg-red-500 focus-visible:outline-red-600"
)
func Button(bType, text string, primary, danger bool) g.Node {
return h.Button(
h.Type(bType),
g.Text(text),
c.Classes{
buttonBase: true,
buttonDanger: danger,
buttonPrimary: primary,
},
)
}
Changing the padding of a button is now a matter of updating the padding of buttonBase
. (All of this, just to have mentioned it, is still more complex than a single CSS class and potentially a CSS variable for the actual padding value.)
So all Tailwind really is doing is taking its large amount of CSS and outputting less CSS. And in exchange I have to audit 30 packages. This is not a very compelling offer. Especially considering there are many small frameworks out there that consist of a single CSS file to vendor from the begin with. And while CSS is turing complete and someone could certainly implement a vulnerability somehow, I will take my chances.
Cheater!
You might rightfully call out that this whole idea assumes a capable template engine, which is a library and therefore a supply chain risk. Which is correct. Remember I do not hate waffles because I like pancakes.
You will likely not write everything from scratch all the time. This is neither feasible nor economical. The idea is to reduce risk. Fully making it go away is likely far too expensive for most projects.
So in my case the question is if reducing CSS is worth 30 packages of a known, problematic source. And the answer is no. Especially as tooling allows me to get extremely close to the comfort of Tailwind but with only the template engine I would use anyway and a random CSS only framework.
The same goes for other technologies like frontend frameworks to build applications. I can easily review HTMX or Alpine and vendor a single file. I do not think you could pay me enough to review the entirety of React and commonly used libraries. While there is a place for these frameworks, there is also a lot you can do with the smaller, easier to audit alternatives if you are open to a small change in workflow and mental model.
Standardization
One thing to keep in mind when evaluating risk is also evaluating what a solution is worth. And the worth of a larger framework shifts dramatically as team size increases.
If you have to hire fast, have to hire people with various backgrounds or want to make onboarding as easy as possible a well known framework shines. For anything you hand roll people need to learn it and adopt to the mental model. React is React. Tailwind is Tailwind.
Additionally tooling such as LLMs, linters and existing pipelines integrate well with larger frameworks where you would have to manually create all of this for your in house solution. As with many other things security cannot be looked at in isolation and does not exist in a vacuum. Things would be so much easier if this were the case.
Personal choice
I like Tailwind. While it is not perfect and there is a lot of valid criticism it works for me. And it makes things easy when working with others. But at the same time I have to say that the advantages of standardization have to outweigh the real and theoretical risk.
This is not isolated to JavaScript and Tailwind. Since Go shipped http.ServeMux
with path variables I reach for a third party router far less often. Far less often. In one of my currently ongoing projects I am still using echo. There are internal packages built out for it already, multiple people work on the same code and are used to echo and it ships a few nice features I would not want to hand roll.
This thought process is part of my decision making framework when looking at technology, not only to see if it is worth to add it to my projects but also if it is worth to continue to use it. For now I will be exploring a CSS framework without dependencies combined with a template engine and see how usable this approach is at different scales.
posted on Oct. 6, 2025, 5:01 p.m. in security101, software engineering
Article series
Security 101
In this article series I discuss information security in the most pragmatic fashion I can think of making it more accessible for individuals and startups.
This is by no means a complete guide to everything you ever wanted to know, nor will it cover every state of the art aspect in existence. Instead I focus on practical advice for day to day operations.
Small steps will bring you a long way to not be subject of the next "we take customer data security very seriously" artcile.