Tailwind CSS gets dismissed as "inline styles with extra steps" by people who haven't used it past a tutorial, and that criticism misses the actual reason it won: constraint-based design tokens baked into every class name.
Tailwind CSS is a utility-first CSS framework where you compose designs directly in markup using small, single-purpose classes like flex, pt-4, and text-gray-600, instead of writing custom CSS files. The utilities aren't arbitrary — they're generated from a design system (spacing scale, color palette, type scale) defined in your config, which is what actually prevents the inconsistency that plain inline styles cause.
Why Tailwind Matters (and When to Skip It)
Traditional CSS forces a naming problem — every new component needs a class name, and over time codebases accumulate .card, .card-2, .card-featured, .card-alt with overlapping, half-forgotten styles. Tailwind sidesteps naming entirely: styles live where they're used, and dead styles are obviously dead because there's no separate stylesheet to audit.
Skip Tailwind for small marketing sites where hand-written semantic CSS is genuinely simpler, or for teams that have a strong existing design-system-as-CSS-classes workflow that already works. For component-driven apps (React, Vue, Svelte), Tailwind's colocation advantage is hard to beat.
Getting Started with Tailwind
Install and configure:
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
A typical component, entirely in utility classes:
function Button({ children }: { children: React.ReactNode }) {
return (
<button className="px-4 py-2 rounded-lg bg-blue-600 text-white font-medium hover:bg-blue-700 transition-colors">
{children}
</button>
);
}
Every value — px-4, rounded-lg, bg-blue-600 — maps to a value in your design token scale, not an arbitrary pixel value. That's the actual difference from inline styles.
Core Tailwind Concepts Every Developer Should Know
Responsive and state variants prefix any utility. md:flex-row, hover:bg-blue-700, dark:text-white — the same syntax works across breakpoints, pseudo-classes, and dark mode, without writing a single media query by hand.
<div className="flex flex-col md:flex-row gap-4">
@apply extracts repeated utility combinations into a custom class, for the rare case where you genuinely need a reusable CSS class name (like styling markdown-rendered HTML you don't control the markup of):
.prose-link {
@apply text-blue-600 underline underline-offset-2 hover:text-blue-800;
}
Arbitrary values escape the design system when you need a one-off value: w-[137px] or top-[calc(100%+8px)] — useful, but overusing arbitrary values defeats the point of having a constrained scale in the first place.
JIT (just-in-time) compilation means only the classes you actually use in your source files get generated — Tailwind scans your files and produces a minimal CSS bundle, not the entire utility library.
Common Tailwind Mistakes and How to Fix Them
Mistake 1: className strings that sprawl past 15-20 utilities. A button with 25 chained classes is genuinely harder to read than the semantic-CSS alternative. Fix: extract the component into a proper React/Vue component once a class list gets unwieldy — that's the actual reuse mechanism in Tailwind, not @apply.
Mistake 2: fighting the config instead of using it. Hardcoding bg-[#3b82f6] everywhere instead of adding it to theme.colors in tailwind.config.js recreates the exact inconsistency problem Tailwind is supposed to solve. Fix: extend the theme config for any color, spacing, or font value used more than once.
Mistake 3: not purging unused styles in production. Misconfigured content paths in tailwind.config.js either miss files (styles silently don't apply) or scan too broadly (slower builds). Fix: point content precisely at your component directories.
When Should You Use Tailwind Instead of Plain CSS?
Use it for component-driven UIs where colocating styles with markup speeds up iteration and prevents CSS file sprawl. Reach for plain CSS (or CSS Modules) for highly custom, animation-heavy, or design-system-agnostic visuals where utility classes would fight you more than help.
Tailwind in Production
Set up a component library early (shadcn/ui or your own) rather than letting utility class strings duplicate across every button, card, and input in the app — this is the actual answer to "doesn't this get repetitive," and most teams that dislike Tailwind at scale skipped this step. Also run prettier-plugin-tailwindcss to auto-sort class names consistently — it removes an entire category of pointless diff noise in code review.
If your class lists keep growing past readability, that's not a Tailwind problem — it's a missing component. Extract it.