Tailwind CSS
Status: 🟩 COMPLETE (🟦 LIVING) Last updated: 2026-06-20 Plain-English tagline: A utility-first CSS framework. Instead of writing
.button { padding: 16px; ... }in a separate file, you compose styles directly in your HTML/JSX with classes likep-4 bg-blue-500 text-white.
In plain English
In traditional CSS, you write .button-primary somewhere, then add class="button-primary" to elements. Two files, two places to keep in sync, and naming things is hard.
Tailwind flips this. Instead of naming custom classes, you compose styles directly with pre-defined utility classes:
<button class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">
Click me
</button>Each class does ONE thing: px-4 is “horizontal padding 4 (16px)”, bg-blue-600 is “background color blue-600”, hover:bg-blue-700 is “background blue-700 on hover.”
It looks ugly at first. After a week of use, most developers don’t want to go back. Speed, consistency, and no naming overhead make it the dominant styling approach in modern React/Next.js work in 2026.
Created by Adam Wathan. Open source. Tailwind v4 is current as of mid-2026, with significant changes from v3 — but most projects still target v3 conventions and migrate gradually.
Why it matters
- Speed. You stop context-switching between HTML and CSS files. Style as you build.
- No naming. “What do I call this class?” is a real cognitive cost. Tailwind removes it.
- Consistency. The design system (spacing, colors, font sizes) is baked into the utility names.
p-4is always 16px; you can’t accidentally make it 17px. - Smaller production CSS. Tailwind generates CSS only for the classes you actually use. A complex app’s CSS bundle is often smaller than a hand-written one.
- No conflicts. Two developers writing
.carddifferently can’t clash if neither writes a.cardclass. - shadcn/ui is built on Tailwind. If you use shadcn (recommended), Tailwind is along for the ride.
A 5-minute tour
Spacing
<div class="p-4"> <!-- padding: 16px all sides -->
<div class="px-4 py-2"> <!-- padding 16px horizontal, 8px vertical -->
<div class="mt-8"> <!-- margin-top: 32px -->
<div class="gap-2"> <!-- gap (for flex/grid): 8px -->The number scale: 1 = 4px, 2 = 8px, 3 = 12px, 4 = 16px, 8 = 32px, 16 = 64px. Always 4px-based. You internalize this fast.
Colors
<div class="bg-blue-500"> <!-- background: blue (medium) -->
<div class="text-slate-900"> <!-- text color: slate (dark) -->
<div class="border-gray-200"> <!-- border color: gray (light) -->Color scale: 50 (very light) → 100 → 200 → … → 900 → 950 (very dark). Common base colors: slate, gray, zinc, red, orange, yellow, green, blue, purple, pink. Plus white, black, transparent.
Layout
<div class="flex items-center justify-between gap-4">
<div>Left</div>
<div>Right</div>
</div>
<div class="grid grid-cols-3 gap-4">
<Card /> <Card /> <Card />
</div>Flexbox and Grid wrapped in utility classes. Once memorized, you write layouts faster than typing CSS.
Typography
<h1 class="text-3xl font-bold leading-tight">Title</h1>
<p class="text-base text-slate-600 leading-relaxed">Body</p>text-xs/sm/base/lg/xl/2xl/3xl/4xl… font-thin/light/normal/medium/semibold/bold/black. leading-tight/snug/normal/relaxed/loose.
Responsive
<div class="text-sm md:text-base lg:text-lg">
Small on mobile, medium on tablet, large on desktop
</div>Prefixes: sm: (640px+), md: (768px+), lg: (1024px+), xl: (1280px+), 2xl: (1536px+). Mobile-first by default — base styles apply everywhere, prefixed styles override at that breakpoint and up.
Interactive states
<button class="bg-blue-500 hover:bg-blue-700 focus:ring-2 active:bg-blue-800 disabled:opacity-50">
Click
</button>Prefixes for states: hover:, focus:, focus-within:, active:, disabled:, group-hover: (responds to parent hover), and many more.
Dark mode
<div class="bg-white text-slate-900 dark:bg-slate-900 dark:text-slate-100">
Adapts to dark mode
</div>The dark: prefix activates when <html class="dark"> is set. See How-to: Set up dark mode.
How Tailwind actually works
Tailwind is a build-time tool. During npm run build:
- Tailwind scans your project files (
./app/**/*.{tsx,ts}etc., configured intailwind.config.ts). - For each class it finds (
p-4,bg-blue-500, etc.), it generates the corresponding CSS. - Unused utilities are excluded — your CSS bundle contains only what you used.
The result is a small CSS file (often 10–50KB compressed, even for complex apps) with no manual class authoring.
Configuration
tailwind.config.ts (v3 conventions):
import type { Config } from "tailwindcss";
const config: Config = {
content: [
"./app/**/*.{ts,tsx}",
"./components/**/*.{ts,tsx}",
],
darkMode: "class",
theme: {
extend: {
colors: {
brand: "#FF6B35",
"brand-dark": "#CC5429",
},
fontFamily: {
sans: ["Inter", "system-ui", "sans-serif"],
},
},
},
plugins: [],
};
export default config;content— where Tailwind looks for classesdarkMode—"class"is the modern defaulttheme.extend— your custom additions (extend) or overrides (no extend)plugins— extra functionality
In Tailwind v4, configuration moves to CSS files via @theme directives. The patterns differ; both work; v3 syntax is still most common in mid-2026.
A concrete example: a polished button
<button class="
px-4 py-2
bg-blue-600 hover:bg-blue-700 active:bg-blue-800
text-white font-medium
rounded-lg
shadow-sm hover:shadow
transition-all
disabled:opacity-50 disabled:cursor-not-allowed
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2
">
Save changes
</button>Looks busy. But:
- It works
- It’s accessible (focus ring)
- It handles hover, active, disabled states
- It’s keyboard-navigable
- The result is roughly 40 lines of CSS — generated automatically
In hand-written CSS, this would be:
.btn-primary {
padding: 8px 16px;
background: #2563eb;
color: white;
font-weight: 500;
border-radius: 8px;
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
transition: all 0.15s;
}
.btn-primary:hover { background: #1d4ed8; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.btn-primary:active { background: #1e40af; }
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-primary:focus { outline: none; box-shadow: 0 0 0 3px white, 0 0 0 5px #3b82f6; }Both work. Tailwind eliminates the separate file + the naming + the CSS authoring; you read it inline.
Extracting components — when classes get long
When a button is used 50 times, you’d hate to retype 10 classes everywhere. Two approaches:
React component
function Button({ children, ...rest }: ButtonProps) {
return (
<button {...rest} className="px-4 py-2 bg-blue-600 ...">
{children}
</button>
);
}Use everywhere as <Button>Save</Button>. Pure JSX; nothing Tailwind-specific.
@apply (Tailwind directive)
.btn-primary {
@apply px-4 py-2 bg-blue-600 hover:bg-blue-700 ...;
}Then <button class="btn-primary">. Less common in React projects; component extraction is preferred.
shadcn/ui — components built on Tailwind
ui is a library of beautiful, accessible React components that you copy into your project (rather than installing as a package). Built on Tailwind + Radix UI.
npx shadcn@latest add buttonDrops components/ui/button.tsx into your project. You customize it freely from there. The default styles use Tailwind under the hood.
For modern React apps, the pattern is: Tailwind for layout/custom styling + shadcn/ui for components. Together they’re the fastest path from idea to polished UI.
Common gotchas
-
Class string ordering. Order doesn’t matter for CSS specificity (Tailwind sorts internally) but matters for readability. Use the official Tailwind sort plugin or just write them grouped (
flex items-center ... bg-white ... text-slate-900 ... hover:...). -
Dynamically constructed class names don’t work. Tailwind scans your source files for class names.
text-${color}-500won’t be detected. Use a lookup map:{ red: "text-red-500", blue: "text-blue-500" }[color]. -
The content array must match where your files live. If you add a new folder, update
contentin the config or those files’ classes won’t be generated. -
@applyin component CSS doesn’t include responsive/hover variants by default. Use@apply hover:bg-blue-700carefully; sometimes needs@layer components. -
Conflicting utilities.
p-4 p-8— which wins? The later one, but it’s unclear when wrapped across components. Usetailwind-mergelibrary to deduplicate at runtime if you compose classes from props. -
Specificity issues with shadcn/ui or Radix. Component libraries may need higher specificity to override. Use
!importantsparingly or restructure. -
Production CSS missing classes. Build output looks right in dev but classes don’t apply in production. Usually: file path not in
content, or class name dynamically constructed. -
Tailwind v3 → v4 migration. v4 has significant changes (CSS-first config, new variants, performance). Plan migrations deliberately; don’t auto-upgrade major versions.
-
space-x-*vsgap-*.space-x-4adds margin to children;gap-4is grid/flex gap.gap-*is more reliable and works in flex/grid both. -
Dark mode class on
<html>vs<body>. Tailwind looks fordarkclass on the same level or above. Class on<html>(the recommended approach) works everywhere. -
Inline classes get long. 15-class buttons happen. Refactor to components or accept the verbosity.
-
Tailwind alone doesn’t give you components. It’s a styling system, not a UI library. Use shadcn/ui, Radix, or build your own.
See also
- CSS 🟩 — what Tailwind generates
- HTML đźź©
- ui 🟩 — components built on Tailwind
- Next.js 🟩 🟦 — Tailwind is default in new Next.js apps
- React đźź©
- Dark mode đźź©
- How-to: Set up dark mode đźź©
- Tailwind gotchas 🟥
- Glossary: Tailwind CSS
Sources
- Tailwind CSS docs — excellent, searchable reference
- Tailwind UI — paid library of pre-built designs
- shadcn/ui
- Refactoring UI — Adam Wathan’s book on design principles