DEV Community

Cover image for Why I No Longer Use Tailwind (and I Don't Regret It!)
seyed mojtaba shadab
seyed mojtaba shadab

Posted on

Why I No Longer Use Tailwind (and I Don't Regret It!)

An Honest Confession

Let me be clear from the start: I'm not saying Tailwind is a bad technology. Like any other tool, Tailwind was created to solve a specific problem, and for that specific purpose, it has genuinely performed well. But the issue is that that "specific problem" is not the same as "the problem of large-scale projects."

I, too, was once in love with Tailwind. Back when I had just started with Next.js, the speed of writing styles with Tailwind felt magical to me. Fast, convenient, painless. Now, after two years, I've used it sparingly in an ICU-Studio project, which was yet another wrong decision.

My Biggest Criticism — Mixing apples and oranges

My biggest issue with Tailwind comes down to one thing: the misplacement of layers of responsibility.

You're essentially writing inline styles inside your component — just in a new format. A bunch of classes that have no meaning behind them. They refer neither to the business domain, nor to the role of that element on the page, nor to its relationship with the other parts.

Let's look at a simple example together:

<!-- Tailwind -->
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:ring-2 focus:ring-blue-300 transition-colors duration-200">
  Buy
</button>

<!-- Semantic CSS -->
<button class="btn btn--primary btn--lg">
  Buy
</button>
Enter fullscreen mode Exit fullscreen mode

Which one can you read and understand within three seconds? Which one can you debug in DevTools? Which one can you change without touching the markup?

The answer is clear. And this is just a small example. Now imagine repeating this pattern across thousands of components.

Tailwind was designed to optimize writing speed at small and medium scales, and in that regard, it is truly unrivaled. But when you enter a project that:

  • Has a lifespan of more than 2 years
  • Has more than 3-4 developers
  • Has a serious design system
  • Needs accessibility and theming

That's when Tailwind turns from a "speed tool" into "technical debt whose interest you pay every single day."

A Response to Three Common Arguments

Argument One: "Well, Tailwind 4 is out and it's made it better!"

Yes, Tailwind 4 has genuinely improved. The Oxide engine is faster, the config has been moved to CSS, it uses native CSS Variables, and content detection has become automatic. These are all good things.

But these are optimizations of the tool, not a solution to the fundamental problem. The issue isn't that Tailwind is slow or that its config is difficult. The issue is that its mental model isn't aligned with the needs of large-scale projects. A faster car that's heading in the wrong direction just reaches the wrong destination faster.

Argument Two: "Well, there's Shadcn or DaisyUI!"

This sentence is exactly what confirms the problem, not what solves it. The fact that you need another layer of abstraction to make Tailwind tolerable means that Tailwind itself, in its pure form, isn't sufficient for real-world projects.

Although, personally, I like daisyUI; it is a favorite project of mine.

Shadcn and DaisyUI are essentially reinventing the wheel of semantic CSS all over again — only this time in the form of JSX. You ultimately need something called Button, not px-4 py-2 bg-blue-500. This is exactly semantic CSS, just with a fresh layer on top.

Argument Three: "Well, you can use ‍@apply!"

It is best to consult the maintainer's perspective. @apply was likely initially considered an anti-pattern, and the documentation explicitly warned against it.

If you need to write @apply to make your code readable, why not just write CSS directly? @apply is a half-baked solution that loses both the advantages of CSS and the advantages of Tailwind.

Why Does the Problem Differ in Large-Scale Projects?

In a small or personal project, Tailwind's speed is highly practical — and this is, in fact, Tailwind's best and most important use case, which I wholeheartedly welcome. But in large-scale projects, several structural problems gradually emerge:

  1. HTML Bloat and Declining Readability
    You're no longer writing markup. You're writing a stylesheet that happens to have taken the shape of HTML. Content drowns in a sea of abbreviated classes. A simple div with 15 classes is no longer a div — it's a mystery.

  2. The Cost of Cognitive Load on the Team
    In a team of 5, every developer has to parse the entire Tailwind dictionary in their head every time they look at a component. This is a cost that isn't felt in small projects, but in a project with 200 components and a 5-year lifespan, it turns into serious technical debt.

  3. Difficulty of Debugging and Review
    A real analysis from a developer shows that in Tailwind, you can't scan the property column the way you can in regular CSS. You have to hunt for bg- with your eyes inside a long, unbroken string. Prettier doesn't help either, because these are string literals.

  4. Lock-in to a Single Tool
    When all your styling lives inside the markup, migrating to anything else — even to plain CSS — is a complete rewrite. Whereas semantic CSS is naturally refactorable, overridable, and replaceable.

  5. Repetition and Lack of DRY
    In Tailwind, a design token like the brand's primary color gets repeated in a thousand different places: bg-blue-500, text-blue-500, border-blue-500, ring-blue-500. Changing it means find & replace across the entire codebase. In semantic CSS, it's a variable. One place. Done.(Of course, this can be controlled, but with more effort.)

A CSS Class Should Be an "Identity," Not an Instruction

I really love this line of yours: "CSS classes were supposed to be an identity, not just a bunch of letters!"

Personally, I prefer that any developer can quickly grasp the nature of an element. Some people, however, like the nature of an element to be a mystery that requires decoding😕

That's exactly it. In semantic CSS:

.card means "this is a card"

.card--featured means "this card is featured"

.card__title means "this is the card's title"

From the name alone, you understand where this element sits in the layout, what it does, and what role it plays. You can read it, understand it, debug it, and explain it to a new team member.

In Tailwind:

.flex items-center justify-between p-4 bg-white rounded-lg shadow-md

What does this mean? Is it a card? A header? A list item? Nobody knows unless they read the entire codebase.

Naming is itself a form of documentation. Tailwind removes that documentation and instead gives you an abbreviation system that only its creator understands.

Who Is Tailwind Suitable For?

So far I've talked about Tailwind's downsides, but the situation really isn't as catastrophic as it may have sounded. My intention was to say that Tailwind isn't a good idea in a real, large-scale project!

But fairness demands that I say Tailwind is excellent for certain scenarios:

  • Small projects and prototypes
  • Single-page landing pages
  • Developers who don't want to get involved in CSS architecture
  • Teams where everyone knows Tailwind at an expert level
  • Projects with a short lifespan where maintainability isn't a priority

But for large, long-term, team-based projects with a serious design system — Tailwind is a weak strategic choice. Not because it's poorly built, but because the problem it solves isn't the problem these projects have.

I absolutely love those who use Tailwind, because they've understood the problem and found a solution. But don't forget that not all problems can be solved with a single solution!

Alternatives — What Instead of Tailwind?

If you're setting Tailwind aside, there are mature and proven options available. But choosing an alternative depends on why you're setting Tailwind aside:

1. CSS Modules — "Real CSS" with Automatic Scoping

CSS Modules automatically scopes a .module.css file. At build time, .title becomes something like .UserCard_title__h2x9p, and you get an object mapping original names to mangled names.

Why choose it:

  • It's pure CSS — no new syntax, no JS-to-CSS bridge. Designers can edit the file directly.
  • It's supported built-in in all modern bundlers (Vite, Next.js, Webpack) — no extra installation or config needed.
  • Zero runtime — exactly like Tailwind, just with a different expression.
  • No naming collisions — it's automatically scoped at the file level.

The cost: Styles live in a separate file from the component, so when editing you have to jump between files. It also doesn't have predefined design-system primitives, so you have to build the spacing scale, color tokens, and breakpoints from scratch.

When it's suitable: Your team has strong CSS expertise and sees Tailwind as "noise." Or you're building an in-house design system that wraps all utilities behind semantic classes.

2. daisyUI — "If You're Going to Stay Dependent, at Least Stay Dependent Smartly"

daisyUI isn't a replacement for Tailwind — it's a semantic layer on top of Tailwind. This is exactly what large projects need but don't find in pure Tailwind.

daisyUI is a collection of CSS classes with semantic naming built from Tailwind utilities. For example, instead of writing bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded, you just write btn btn-primary.

Why this suggestion makes sense for "someone heavily dependent on Tailwind":

  • Readability returns. Your code becomes readable again. card means card, btn-primary means primary button. Exactly what you said in your post: "A CSS class should be an identity."
  • Repetition disappears. Instead of repeating 33 utility classes for every button, you write one btn class. In a 100-page project, that's roughly a 79% reduction in UI code volume.
  • Theming comes ready-made. It has 35 built-in themes and works with CSS Variables, so dark mode and theme switching work without touching component code.
  • 61 ready-made component families — from form and table to navigation and feedback.

But an important warning: daisyUI hides Tailwind's problems, it doesn't eliminate them. You're still in the Tailwind ecosystem. If your project needs a real design system (not just theme switching), daisyUI may ultimately lead you to yet another layer of abstraction.

A point worth mentioning: daisyUI is even more efficient for LLMs. In a 100-file project, Tailwind + daisyUI consumes about 197.5 thousand fewer tokens than pure Tailwind. If you work with AI, this is a real advantage.

3. UI Libraries — When You Want to Escape "Styling" Altogether

Personally, I like this option the most. If your problem is "I don't want to deal with CSS at all," full UI libraries are serious options:

  • Mantine, MUI, Chakra UI: Fully ready-made components with clean APIs. You work with props and components, not classes. For teams that want to focus on logic and leave styling to the library.
  • daisyUI-based libraries: Like Cornet UI for Vue, which adds a typed props/emits layer on top of daisyUI. Or Shadcn/ui, which is built on Tailwind but provides copy-paste-able components.

When it's suitable: Projects whose UI is standard and don't need a unique design language. Or teams that want to maximize speed and are willing to sacrifice some flexibility.

But be careful: A UI library means you accept that library's opinion about design. If your project needs custom design, sooner or later you'll hit a wall.

Summary

Alternative What it solves What it doesn't solve
CSS Modules Readability, scoping, real CSS Need for a pre-built design system
daisyUI Readability, repetition, theme, speed Doesn't eliminate dependency on Tailwind
UI Library Speed, consistency Design flexibility, deep customization

The final choice depends on exactly what you're tired of in Tailwind:

  • Dirty, unreadable code → daisyUI or CSS Modules
  • Dependency on a single tool → CSS Modules or Vanilla Extract
  • Dealing with CSS → UI library
  • Lack of a design system → daisyUI (if you want Tailwind) or Vanilla Extract

In the End, the Problem Is Fundamentally Flawed

Let me finish with the same sentence I started with:

Tailwind works in the best possible way for the purpose it was built for. In that purpose, there's no doubt. But that purpose is not the purpose of large-scale projects.

The problem is fundamentally this: Tailwind assumes that the main problem with CSS is "writing too much." But the main problem with CSS in large-scale projects has never been writing too much — the problem has been maintenance, readability, and knowledge sharing among team members. Tailwind doesn't solve these three; it postpones them.

And in large-scale projects, postponing a problem is always more expensive than solving it from the start.

I no longer use Tailwind. And I don't regret it.

Which do you choose? Speed today, or peace of mind tomorrow?

Top comments (0)