An opinionated defence of hand-crafted CSS in a utility-class world
There. I said it. Call me a dinosaur. Call me a purist. Tell me I'm fighting progress. I'll be over here, writing beautiful, considered, mine CSS — and enjoying every second of it.
Tailwind CSS is enormously popular. Millions of developers reach for it on every project. The tooling is great, the community is huge, and the documentation is genuinely excellent. I respect all of that. And I still don't want it anywhere near my projects.
Let me explain why.
The Pumpkin Pie Problem
Here's an analogy that I can't shake.
Making a pumpkin pie from scratch means: roasting a whole pumpkin, scooping and blending the flesh, building your custard from scratch with cream and eggs and warming spices — nutmeg, cinnamon, a little ginger — making your pastry by hand, feeling the butter come together with the flour under your fingertips, crimping the edges, blind baking the shell, pouring in the filling, baking it until the kitchen smells incredible.
Using Tailwind is more like: buying a ready-made pastry crust from the supermarket, cracking open a tin of pumpkin purée, stirring in a spice packet, pouring it in, and then telling everyone you made a pumpkin pie from scratch.
And here's the thing about pumpkin pie specifically — unlike apple, there's almost nothing to hide behind. The filling comes out of a tin. The crust came from a packet. There's no chopping, no layering, no real technique involved. You've essentially just operated an oven. Sure, there's a pie at the end. It'll probably taste fine. People will eat it. But you didn't make it. Not really. You assembled it.
I know some of you are already typing your rebuttals. "But the end user doesn't care how you made it!" "Efficiency is what matters!" "Ship faster, iterate quicker!"
And you're not wrong — those things matter. But they're not the only things that matter. And in the rush to ship faster, I think we've lost sight of something important.
CSS Is a Craft. Treat It Like One.
Writing CSS well is a skill. A genuine, hard-won, takes-years-to-really-get-good-at skill.
Understanding the cascade. Knowing when to use custom properties and why. Building a coherent spacing system that breathes consistently across a whole design. Writing selectors that are specific enough to do their job and no more. Reaching for clamp() and letting the browser do the responsive work for you. Crafting animations that feel like they belong to the design rather than being bolted on.
This is craft in the same way that woodwork is craft, or cooking is craft. It's the difference between a furniture maker who understands grain and joinery, and someone who assembles flat-pack with an Allen key. Both produce a table. Only one of them really made a table.
Kevin Powell — if you don't follow him on YouTube, stop reading this and go watch a few videos, then come back — has spent years demonstrating exactly this. His channel is a masterclass in what CSS can do when you actually sit down and learn it properly. He makes CSS look joyful, because when you understand it, it genuinely is. He doesn't need utility classes to achieve elegant, responsive, maintainable layouts. He uses the language.
The thing that strikes me watching his content is how confident it is. There's no wrestling with tooling, no hunting through documentation to remember which arbitrary class name produces which pixel value. Just a developer who knows their medium, working with it directly.
That confidence only comes from practice. And you can't practice what you outsource.
What We Give Up
When you reach for Tailwind by default, here's what actually happens — whether you notice it or not.
You stop learning CSS. If you're always translating design intent into utility classes, you never develop the intuition for writing a rule directly. The gap between "I know what I want this to look like" and "I know how to write the CSS to achieve it" never closes. Junior developers especially suffer here — they can ship Tailwind fast, but ask them to debug a stacking context issue or explain why their flexbox isn't aligning the way they expect, and they're lost.
Your HTML becomes noise. Look at a component written in Tailwind. Really look at it. className="flex items-center justify-between px-4 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" — that's a button. The semantic signal is completely buried. The HTML is no longer a document; it's a stylesheet written in the wrong place.
Every project looks the same. Tailwind has a house style. It has a default scale. It has a palette. Even when you customise it, the bones are the same. I've reviewed codebases from companies across the industry and I can almost always tell instantly whether they're using Tailwind. Not because the designs are bad — they're often quite good — but because there's a sameness to them. A kind of interchangeable quality. The quirks, the character, the intentional weirdness that makes a product feel designed rather than generated — that tends to get smoothed away.
You become dependent on the abstraction. What happens when Tailwind changes its API? When class-variance-authority goes out of fashion and everyone moves on to the next thing? You've written tens of thousands of lines of code tightly coupled to a specific flavour of a specific tool. Hand-crafted CSS doesn't have this problem. CSS is CSS. It was there before Tailwind, and it'll be there long after.
"But What About Large Teams?"
This is the serious argument for Tailwind, and I'll give it its due. On large teams, shared conventions matter. If everyone is writing their own CSS in their own style, you get chaos. Inconsistent spacing, competing naming conventions, specificity wars, dead code that nobody dares delete.
Tailwind solves a real problem here. I understand why teams reach for it.
But the solution to inconsistent CSS isn't to stop writing CSS — it's to write better CSS together. A well-maintained design token system. A sensible component architecture. A style guide that the team actually follows. These are harder things to build than installing a package, yes. But they produce better outcomes. They produce a codebase that a developer — any developer — can open and understand without needing to know what text-slate-700 means.
The Feeling of Writing Good CSS
I want to end on something that doesn't get talked about enough in these conversations, because it's subjective, and subjective things get dismissed as irrelevant in technical discussions.
There's a feeling to writing CSS well. To building a component from scratch and having it sing. To watching a layout adapt gracefully across viewport sizes because you set it up correctly with a bit of grid and some thoughtful min() and max(). To seeing an animation you hand-crafted land exactly right — easing, timing, all of it — and knowing you built that.
It's the same feeling the baker gets when the crust comes out perfect. The same feeling the woodworker gets when two joints fit together without a gap.
Tailwind is a very good ready-made pastry crust. But I'd rather learn to make my own.
If you want to fall back in love with CSS as a language, start with Kevin Powell's YouTube channel. He's the most patient, thorough, genuinely enthusiastic CSS educator working today — and watching his content will make you a better developer.
Top comments (42)
Fascinating post, I can’t get it out of my head 🙂 I was going to leave a detailed comment, but I think I might end up writing a whole post about this next week 😄
Respect for both of you keeping it civil and providing your opinion to why you like/dislike Tailwind lol.
Also, @freshcaffeine, thanks for sharing your opinion on Tailwind. Good post :)
I totally agreed your point of view. If someone have time to make a real good hand writed CSS then that is respectfull way.
I feel like I'm always driving in the opposite direction on the highway. I'm the same way with Tailwind. I mean, I started using it right when I started going in a no build, handmade HTML direction, and that meant that the only thing I learned to write down by heart was the Tailwind CDN link. I did this because I use Tailwind for layout design by default. I find it practical because I just look at the code and I can immediately see what layout / responsibility rules the given structure works according to.
E.g. grid gap-2 is the vertical list, flex gap-2 horizontal list. If I really need to make a more complex tailiwind class, I write them not one after the other but one below the other, so that the idea is more readable.
Here's a bad example of using tailwind and a handwritten program:
dev.to/pengeszikra/chuck-norris-to...
However, Tailwind also has its limits (before the 3rd, anyway) because 3D CSS rendering wasn't possible for it at the time. Here's this 3D card game where the 3D engine is actually implemented with CSS, and I think it works quite nicely:
dev.to/pengeszikra/javascript-grea...
That 3D Card game is really neat! Thanks for sharing!
Thx for mention Kevin Powell I was watch a few videos of them and each is very great and imortant like this one:
Highly recommended for every one.
My analogy might be more like, "CSS is making a pumpkin pie from scratch. Tailwind is buying a pie kit that's pumped full of colours and preservatives".
I don't have much of a concern that people are losing the ability to craft CSS. I mean, that's probably happening, but it doesn't matter in the grand scheme. What we want is easily-maintainable, developer- and user-friendly, accessibility- and structure-promoting code. Eventually in the fullness of LLM time, this will probably all become some declarative, "make page brown with pretty sparkly words" anyway, right?
Tailwind reinvents things and makes every single one of those things worse. It just does. Try making a component that will look beautiful in a sidebar or a header without passing that information to it via props to update the HTML, or falling back on a custom class - a practice that Tailwind specifically says to avoid.
Try having an alternate theme for a holiday celebration. Try tracking down a bug while avoiding all the extra magic TW runs at build time. Tear your hair out.
I've been commenting over on I love Tailwind... as well. I agree with both of you for most of the salient points but still end up on, "Tailwind is a massive red flag for me".
Since when was Tailwind an either-or proposition? You use it when it makes sense, which is a lot of the time, especially when working with languages that support template partials/components. When you have something that isn't supported or is so custom it makes sense to, you write custom CSS. Tailwind makes it a breeze to incorporate that customisation into your Tailwind build so that it feels part of it.
To take your analogy on: Tailwind is like having a Michelin-Star chef working in your kitchen. If you want top quality pies, you can have them by the dozen. If you want something a bit different, we'll the chef can accommodate that.
If I had a Michelin-Star chef working in my kitchen, and I was presented a pie made from pre-baked crust. They'd be fired on the spot!
Ah, but did you make the chef milk the cow, so he could make the butter, so he could make the pastry? If not, why not?
Thats a bit far - I would certainly expect the butter to be very high quality, from locally produced grass-fed Jersey cattle.
Assembly libraries existed for years, the biggest are Bootstrap and Foundation.
The difference between those libraries and Tailwind is that they are not seen as a base.
People are treating Tailwind as a base to build other libraries on top of, shadcn/ui is the most known example. Shadcn/ui is more closely related to Bootstrap and Foundation than Tailwind.
The large team argument is a false one. When you don't want ambiguity you build a design system.
Yes there will be discussions, but do you think everyone is using Tailwind the same way?
When people are sure they are right they are going to find a way to break out of the box.
And when people in your company create a design system you can actually talk to them about your ideas. With Tailwind there is more distance because they need to think about the whole world using their library.
I think Tailwind should not be used as a base. Use it how the classes are meant to be used, as a utility. There is nothing wrong with that concept. When you have outliers in your design they shouldn't become part of the base.
When I look through pumpkin pie glasses, Tailwind is not the store bought dough or the pumpkin in a can. It are the replacements of the dough and the pumpkin. You can learn to love the taste of those ingredients, tolerate them up to a certain level, or don't like them.
Writing proper css is way easier than learning tailwind. And if you don’t know about css, how it works behind the curtain how can you use tailwind? What happened to separation of concerns? And your html just turns into a friggin mess, unreadable. Tailwind introduces more problems than it solves imho.
I used to hate CSS, but after fighting with it for years, not understanding why different components never looked right. I have come to love it, and I was easily able to transfer these skills to work with Tailwind and customise components to my liking.
I also highly recommend Kevin's videos, as well as works from Jen Kramer or Josh W. Comeau. They simplified a lot of concepts that never would have clicked before.
I totally agree about the clutter! I actually got so frustrated that I started building my own framework to solve exactly this. Going to be a LONG process, however. My main goal, is instead of having like 50 classes on one element, have less than 5. And not code random things that many will not use often.
The strongest argument here isn't aesthetic — it's that CSS is one of the most stable skills in web dev (selectors I wrote 15 years ago still work) while we've cycled through Bootstrap → BEM → CSS-in-JS → Tailwind in the same window. Every dev whose mental model is the framework instead of the language has had to re-learn fundamentals every cycle. The ones who learned CSS just kept shipping.
It's great for prototypes or internal apps you want quick turnaround on but that markup noise and lack of logical semantics in your UI is a detractor for me.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.