Skip to main content
ASoc
Tutorial

Is Tailwind Good or Bad? The Trade, Measured Against a Real Codebase

A real 19-class hero attribute, the 'keep every class' policy that keeps it that way, and the one compiled stylesheet — 81,866 bytes for 111 products — that pays for it.

The ASoc Team8 min read

Tailwind is neither — it's a tradeoff, and the honest version of "is Tailwind good or bad" is what you get and what you give up. This storefront's own Hero section carries a single className with 19 utility classes in it, kept that way on purpose by a written policy, not an oversight. That policy, and the tool that makes it survivable, is the actual answer.

The short answer

Tailwind is good for consistency (a fixed spacing and color scale instead of hand-picked pixel values) and shipping speed (no naming, no separate stylesheet to keep in sync), and it's bad for raw HTML readability, since styling and structure live in the same attribute. Whether that trade is worth it depends on whether your team has tooling to manage the verbosity — this codebase's answer is yes, and the policy below is why.

The debate, as it actually shows up in this codebase

Every argument in the "Tailwind is bad" camp is really one complaint: class strings get long, and long class strings are hard to scan. Here is the longest one in this storefront's hero section, unedited:

absolute -right-[120px] -bottom-2 aspect-[204/277] w-full max-w-[204px]
overflow-hidden rounded-t-[10px] border-[.6px] border-b-0 border-stroke
bg-white shadow-[-30.119px_0px_87.847px_0px_rgba(16,24,40,0.10)]
max-xl:-right-16 max-xl:max-w-[190px] max-lg:-right-12 max-lg:max-w-[150px]
max-md:right-0

Nineteen classes, one attribute. Read cold, that string tells you almost nothing about what the element is — you have to trace positioning, sizing, a raw box-shadow value, and three responsive overrides before the picture forms. That's the entire "bad" case, and it's correct: this genuinely is harder to scan than a class name like .hero-decoration-card would be. It isn't a one-off either — the header's own mobile-menu-trigger button carries 21 classes in the same shape (layout, color, three hover: states, three max-xl: breakpoint overrides), which is the pattern across this codebase's interactive elements generally: state and responsiveness both live inline, not in a separate rule.

Why this codebase keeps that shape anyway

The class-soup critique assumes the alternative — semantic class names in a separate stylesheet — is free. It isn't, and this repo's own conventions are explicit about the trade it made instead:

Pixel-faithful porting requires keeping every class. This project's own house rule (CLAUDE.md) states it directly: "Section markup is a faithful conversion of the source page; preserve exact Tailwind classes when refactoring (class order is irrelevant, but keep every class)." That rule only makes sense because the classes ARE the specification — extracting them into a named component class would mean re-deriving the design from a screenshot every time a section gets touched, instead of diffing a class list against the original markup. The verbosity is the price of an exact, checkable port.

prettier-plugin-tailwindcss removes the part of the complaint that's actually about disorganization, not length. A hand-maintained long class string drifts — the same handful of utilities end up in a different order in every file, and code review can't tell a meaningful reorder from noise. This repo's .prettierrc.json runs that plugin on every save, which canonicalizes class order deterministically (layout, then box model, then typography, then color, then state variants) so two engineers writing the identical set of utilities always produce byte-identical output. The 19-class string above is long, but it is never inconsistently long — every file in this codebase orders its classes the same way, because a tool does it, not a convention someone has to remember.

The part of "good" that's actually measurable: one stylesheet, not one per page

The strongest pro-Tailwind claim — the compiled stylesheet stops growing once the same utilities repeat — is checkable against this repo's own build output rather than taken on faith. A production build of every marketing page, all 111 product pages, every blog post and every framework spoke compiles to one CSS file, 81,866 bytes. Not one stylesheet per route, not one per component: every page in the app shares that single file, because Tailwind's output is deduplicated at the utility level — p-4 compiles to one rule regardless of how many components use it. A hand-written CSS codebase at this scale (111 products, dozens of shared sections) would need either a naming discipline strict enough to prevent duplicate rules across files, or accept that the same padding value gets redeclared under a dozen different class names across a growing set of stylesheets. Verbose className attributes are the visible cost; a stylesheet that doesn't grow per page is the trade you're making for it.

Good vs. bad, scored against what actually costs something

ConcernThe critiqueWhat actually happens here
Readability of one class attributeGenuinely worse than a semantic class nameTrue and unmitigated — a 19-class string is a 19-class string
Consistency across a 111-product catalogUtility classes drift in ordering without disciplineSolved by prettier-plugin-tailwindcss running on every save
Refactor safetyExtracting a component risks silently dropping a classMitigated by the "keep every class" rule making omissions a diffable regression, not a silent one
Design-token consistency (spacing, color)N/A — proponents' strongest pointp-4 is the same padding everywhere; no margin: 23px eyeballed once and never repeated
A new stylesheet to maintainNot a Tailwind costZero — no CSS file grows in parallel with the components
Onboarding costUtility-class vocabulary to learnOne @theme block in globals.css is the entire vocabulary, not a growing stylesheet history to read through

Troubleshooting

SymptomCauseFix
Class order looks random across filesNo formatter enforcing a canonical orderInstall and run prettier-plugin-tailwindcss — it sorts deterministically, not alphabetically
A refactor "cleaned up" a component and something looks subtly differentA utility class got dropped while extracting a shared componentDiff the old and new class lists directly; don't trust a visual re-read for a 15+ class string
Two components that should look identical render slightly differentlyThe same utilities were typed in a different order by hand before a formatter existed, or a later-loaded stylesheet changed cascade orderRun the Tailwind sort plugin and confirm both class lists are byte-identical where they're supposed to be
Team argues about "is this too many utility classes"No agreed threshold, so it's a taste argument every PRExtract a component (not a CSS class) once the SAME class list repeats 3+ times — that's a reuse signal, not a length one
New teammate says the JSX is unreadableTrue on first exposure, before the pattern is familiarPoint at the @theme tokens (primary, gray-25…950) instead of the raw utility list — the design vocabulary is small even when a single className is long

Frequently asked questions

Is Tailwind CSS good for large projects? It's proven at this project's scale — 111 catalog products, a shared @theme token set, zero JS config files. The risk at scale isn't Tailwind itself, it's unmanaged class-string drift, which a formatter (not a naming convention) solves.

Does Tailwind make HTML harder to read? Yes, for any single long className. That's not a myth — it's the one honest cost, and this post's example is a real 19-class string from a shipped page.

Why not just extract components to avoid long class strings? This codebase does, wherever a class list repeats — that's what a molecule like FeatureCard is. Extraction is the right fix for repeated verbosity; it isn't a fix for a one-off decorative element like the hero card above, which exists exactly once and doesn't need a name.

Is Tailwind slower to build than plain CSS? No — the compiled stylesheet only grows with distinct utility combinations actually used, and duplicate utilities across components cost nothing extra. Bundle size is a non-issue at this project's scale (81,866 bytes for the entire site's CSS); the actual cost this post is about is human readability, not runtime performance.

Does everyone on a team need to memorize the utility class names? Mostly, yes, the same way any team memorizes a component library's prop names — the difference is Tailwind's vocabulary is one @theme block (42 custom properties here) rather than a growing set of component APIs across separate files. Editor tooling (class-name autocomplete, hover previews) closes most of the gap for a newcomer faster than reading an unfamiliar CSS file would.

Where to take this next

The Tailwind class reference, read against a real codebase is the companion audit — the same @theme token set, checked against this site's actual className distribution rather than the documentation's list. Tailwind v4 config, mapped to this repo covers the zero-JS-config setup that keeps the vocabulary in one file.

Templates in this post

ASoc Sentinel, ASoc Signal and ASoc Sterling ship with the same prettier-plugin-tailwindcss setup pre-wired, so class order stays consistent from the first commit you make on top of them.

Browse the full sets: Next.js landing page templates, Tailwind landing page templates.

Keep reading

Tutorial9 min read

Tailwind Grid: 26 Files, and Not One col-span

A 538-page site's whole grid vocabulary is three utilities — plus the display:contents trick that reorders a product page on mobile without duplicating state.

Read more
Tutorial9 min read

Tailwind Height: 139 Uses, Zero of Them Plain h-screen

This codebase's h-* utilities are mostly icon sizing. The one true viewport lock is h-[100dvh], not h-screen — because a static 100vh lies on mobile.

Read more