ServicesLabProcessAboutBlogFAQGet in Touch
HomeBlog

Every Tailwind Take You've Read Is Wrong

Class soup isn't a problem. Config lock-in isn't a problem anymore. There's one thing worth watching, and it's on you, not the tool.

I learned CSS properly by using Tailwind. Flex, grid, spacing scales, responsive breakpoints, all of it clicked faster with utility classes than it ever did staring at a stylesheet. So when I see the same tired Tailwind criticism recycled every few months, it bugs me, because it’s usually made by people arguing with a version of the tool that either never existed or hasn’t existed for years.

Let me go through the big ones.

“It violates separation of concerns”

Someone posts a div with forty utility classes on it, someone else says this breaks the sacred wall between HTML and CSS, and both sides act like this is the crux of the matter. It isn’t.

HTML and CSS were never separate concerns. That idea was always a polite fiction. A <button> and a <span> styled to look like a button are not interchangeable, no matter how identical your stylesheet makes them. Semantics, accessibility, and layout all live in the markup as much as the CSS. The class attribute has been carrying presentation information since class="clearfix" was a thing people typed unironically. Utility classes just make the coupling explicit instead of pretending a .card selector three files away is somehow more principled than flex gap-4 rounded-lg p-6 sitting right there on the element. If anything, co-location is a readability win: I can see what an element looks like without opening a second file and mentally diffing a cascade.

“Your design tokens are locked in a JavaScript config”

This one was true for v3, and it was the strongest criticism Tailwind had. Your palette, spacing scale, and breakpoints lived in tailwind.config.js, got compiled into class names, and had no existence as CSS. Getting a color out to a design tool, a native app, or an email template meant walking a JS object and regenerating something portable. Runtime theming meant fighting the compiler.

Tailwind v4 shipped in January 2025 and ended this argument. Theme values are defined in CSS with @theme, they’re real custom properties, they’re emitted in the output, and bg-brand-600 resolves to var(--color-brand-600). Override a token in a scoped stylesheet and the utilities follow. Cascade layers are native. Container queries are built in. Most of what people used to say “just use native CSS instead” to get, Tailwind now does with native CSS.

If your objection to Tailwind in 2026 is still “the tokens are locked in a config file,” you’re arguing with a version that’s been superseded for over a year.

“Utilities out-specificity your components”

Wrong mechanism. Utilities are single-class specificity, same as .card. They win by source order (the utilities layer comes last), and in v4 that’s a literal @layer, so you can control it. If a utility is stomping your component style, that’s a layering choice.

The one thing worth watching

There is one thing to be careful about. It isn’t Tailwind’s fault; it’s a habit Tailwind makes easy to fall into.

The utility class is the only interface most teams ever build to their design system. Engineers learn the classes, not the tokens. bg-brand-600 is what people type; --color-brand-600 is an implementation detail nobody looks at. When someone needs a slightly different blue for one screen, the path of least resistance is to add --color-brand-650 to the theme block and ship. Nobody removes it later. Nobody audits whether brand still means what the design team thinks it means, because the theme block is something the compiler reads and people don’t.

Two years in, the theme is an append-only log of every override anyone ever needed once. Eleven grays, three of them identical. brand-600 remapped for a one-off white-label build and never remapped back. The tokens are portable now, but the thing you’d be porting is a mess, and the actual design system exists as tribal knowledge of which classes to reach for.

This drift happens in any system where the generated surface is more legible than the source underneath it. Hand-rolled utility layers rot the same way. Tailwind is just the popular example, and the one where it’s easiest to miss because everything still compiles.

The fix is a decision rather than a tool swap: treat the tokens as the product and the utilities as a convenience layer over them. Review the theme block like an API surface, because it is one. Give tokens semantic names (--surface-raised, not --gray-450) and make a PR that adds one justify why an existing one didn’t work. Have components consume tokens by name so overriding a variable actually rethemes the thing. Tailwind v4 makes this easier than it’s ever been. It just doesn’t make you do it.

This is the model I built ARC UI around: tokens as real custom properties, consumed directly by the components, no config layer between the values and the page. A consumer rethemes by overriding a variable in their own stylesheet. If you want to see what that looks like in practice, the docs are at arcui.dev. It also works fine next to Tailwind.

Tailwind is a great tool. Most of the criticism of it is stale or confused. The real risk is treating the class vocabulary as the whole system and letting the layer underneath rot, and that one’s on you.

Content may be edited with the help of AI. All content has been reviewed by a human before being published.

Work with Arclight

Have a project in mind?

I design and ship custom software — from early concept to production. Tell me what you're building and we'll figure it out together.

Start a ProjectView Services
NavigateServicesLabProcessAboutBlogFAQMoreFractionalColorado SpringsTech StackBrandGet in Touchhello@arclight.build+1 (719) 337-4490Colorado Springs, COStart a Project