Two posts appeared on Dev.to a few days ago. One titled "I Love Tailwind — Sorry Not Sorry." The other: "I Don't Like Tailwind — Sorry Not Sorry."
Same format. Opposite conclusions. Hundreds of comments. The community tearing itself apart over utility classes.
I've seen this fight before. So have you.
Microservices vs monoliths. Raw SQL vs ORMs. REST vs GraphQL. Vim vs Emacs if you've been around long enough. The technology changes. The argument doesn't.
It's always about the same thing: should you use primitives directly, or build an abstraction on top of them?
Tailwind gives you utility classes. Small, single-purpose, composable. That's the primitive layer. And at that level, it's genuinely well-designed. The design tokens are consistent. The naming conventions make sense. The build system strips what you don't use.
The problem isn't Tailwind. The problem is using the primitive layer as your application layer.
When your HTML looks like this:
<a class="mt-4 px-6 py-2 bg-blue-500 hover:bg-blue-700 text-white font-bold rounded-lg shadow-md transition-colors duration-200">example</a>
You haven't eliminated CSS complexity. You've moved it. It's the same information, scattered across templates instead of collected in stylesheets. At scale, those class strings become just as unreadable as the CSS they replaced. Arguably worse — because at least CSS has selectors that name things.
The teams that love Tailwind? They're not writing those class strings everywhere. They're using it as a foundation for component libraries. DaisyUI. Shadcn. Headless UI. They write the utility classes once, inside a component, and the rest of the application uses the component. The abstraction layer does the work.
This is the same principle as software architecture. You don't scatter database queries across your controllers. You build a model layer. You don't scatter HTTP calls across your views. You build a service layer. The primitive exists so you can build on top of it, not so you can use it directly in every file.
Microservices vs monoliths is the same argument at a different scale. Microservices are the primitives — small, single-purpose, independently deployable. Powerful at the infrastructure level. But if every feature requires coordinating five services, you haven't reduced complexity. You've distributed it. The teams that succeed with microservices are the ones that build the right abstraction layer on top: API gateways, service meshes, shared libraries, or — more often than the industry admits — they merge services back into a modular monolith and keep the boundaries without the network calls.
The pattern repeats because the underlying tension is real. Primitives give you power and flexibility. Abstractions give you readability and maintainability. You need both. The question is where the boundary sits.
Use Tailwind? Fine. Build components on top of it. Use microservices? Fine. Make sure the abstraction layer justifies the distribution cost. Use raw SQL? Fine. Wrap it in something that names what it does.
The tool is never the problem. The problem is mistaking the primitive layer for the finished architecture.
Every few years we have this argument again, wearing different clothes. The answer is always the same: build on top of your primitives. Don't scatter them everywhere.
United States
NORTH AMERICA
Related News

‘The Testaments’ Just Brought Back Another Surprising ‘Handmaid’s Tale’ Character
2h ago
Islamic Medicine (2018)
April 19, 2026
LLM and Generative AI Interview Questions with Answers 2026
April 20, 2026
How nylas mcp uninstall Works: Remove MCP integration from an AI assistant
April 19, 2026
🌍 Earth's Last Letter
April 20, 2026