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.
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
| Concern | The critique | What actually happens here |
|---|---|---|
| Readability of one class attribute | Genuinely worse than a semantic class name | True and unmitigated — a 19-class string is a 19-class string |
| Consistency across a 111-product catalog | Utility classes drift in ordering without discipline | Solved by prettier-plugin-tailwindcss running on every save |
| Refactor safety | Extracting a component risks silently dropping a class | Mitigated by the "keep every class" rule making omissions a diffable regression, not a silent one |
| Design-token consistency (spacing, color) | N/A — proponents' strongest point | p-4 is the same padding everywhere; no margin: 23px eyeballed once and never repeated |
| A new stylesheet to maintain | Not a Tailwind cost | Zero — no CSS file grows in parallel with the components |
| Onboarding cost | Utility-class vocabulary to learn | One @theme block in globals.css is the entire vocabulary, not a growing stylesheet history to read through |
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Class order looks random across files | No formatter enforcing a canonical order | Install and run prettier-plugin-tailwindcss — it sorts deterministically, not alphabetically |
| A refactor "cleaned up" a component and something looks subtly different | A utility class got dropped while extracting a shared component | Diff 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 differently | The same utilities were typed in a different order by hand before a formatter existed, or a later-loaded stylesheet changed cascade order | Run 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 PR | Extract 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 unreadable | True on first exposure, before the pattern is familiar | Point 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.
