Guide
Tailwind CSS fundamentals explained
Traditional CSS asks you to invent class names, hop between HTML and stylesheets,
and fight specificity wars as components grow. Tailwind CSS flips
the model: you compose utility classes directly in markup —
flex gap-4 p-6 rounded-lg bg-white shadow-md — and a build step
emits only the rules your project actually uses. The result is faster iteration,
consistent spacing and color scales, and CSS bundles that stay small even on large
apps. Tailwind v4 (2025) simplifies configuration with CSS-native @theme
tokens and content scanning via @source. This guide covers the
utility-first mindset, core class families, responsive and state variants, v4
architecture, framework integration, a Harbor Supply order-dashboard worked example,
a styling approach decision table, pitfalls, and a practitioner checklist — building
on our
CSS fundamentals guide,
Flexbox and Grid guide,
and
responsive web design guide.
Utility-first CSS: what changes
In semantic CSS you might write .card-header { margin-bottom: 1rem; font-weight: 600; }
and reuse that class everywhere headers appear inside cards. Tailwind instead
exposes atomic utilities — mb-4 font-semibold — that mean the same
thing regardless of context. You trade bespoke class names for a constrained
design system baked into the class list.
Benefits show up in team velocity:
- No naming fatigue — you never debate whether a wrapper is
.order-summaryor.checkout-panel. - Predictable scale — spacing uses a fixed rem-based ladder (
p-2,p-4,p-6) instead of arbitrary pixel values scattered across files. - Locality of behavior — you see layout and color in the same file as structure; refactors do not orphan CSS rules.
- Tree-shaken output — unused utilities never ship; production CSS is typically tens of kilobytes, not hundreds.
The trade-off is verbose HTML and a learning curve for the class vocabulary. Tailwind works best when you already understand how CSS cascade and the box model work; utilities are shortcuts, not a substitute for layout fundamentals.
Core utility families
Tailwind groups thousands of classes into predictable prefixes. You do not memorize all of them — learn the families and autocomplete the rest.
Layout and spacing
flex, grid, block, hidden control
display. gap-*, p-*, m-*, w-*,
h-*, max-w-* handle dimensions. Flex and grid alignment
use justify-*, items-*, self-* — the same
concepts as raw
Flexbox and Grid,
just abbreviated.
Typography and color
text-sm through text-4xl set font size; font-medium,
font-semibold, font-bold set weight; text-gray-600
or custom tokens like text-ink set color. Background, border, ring,
and shadow utilities mirror the same token palette so hover states stay coherent.
Interactive states
Prefix variants modify a utility under conditions: hover:bg-gray-100,
focus:ring-2, disabled:opacity-50, group-hover:visible.
State variants stack left-to-right: md:hover:bg-blue-600 applies only
on medium screens during hover.
Responsive breakpoints
Mobile-first breakpoints prepend screen sizes: sm: (640px),
md: (768px), lg: (1024px), xl: (1280px),
2xl: (1536px). An unprefixed class applies to all widths; prefixed
classes override at that breakpoint and above. A card grid might be
grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 — one column
on phones, three on desktop without media-query files.
Tailwind v4 architecture
Tailwind v4 is CSS-native. Instead of a JavaScript tailwind.config.js
as the primary configuration surface, you import Tailwind inside your main
stylesheet and declare design tokens with @theme.
@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/utilities.css" layer(utilities);
@source "../src/**/*.{html,tsx,vue}";
@theme {
--color-brand: #0fb47e;
--font-sans: "Inter", system-ui, sans-serif;
--radius-card: 18px;
}
Key v4 concepts:
@source— tells the compiler which files to scan for class names (replaces the oldcontentarray).@theme— extends or overrides the default design token map; custom colors become utilities likebg-brandautomatically.- Standalone CLI —
./bin/tailwindcss -i src/input.css -o dist/style.css --minifywith no PostCSS required for many projects. - Layer control —
@layer theme, components, utilitiesorders cascade so component classes and utilities compose predictably.
v4 also supports optional preflight (Tailwind's opinionated CSS reset). Brownfield
sites sometimes disable preflight to avoid clashing with legacy styles — then add
targeted resets (for example [hidden] { display: none !important; })
where the reset would have handled them.
@apply, components, and when to extract
Purists use utilities inline everywhere. Pragmatists extract repeated clusters
into component classes with @apply:
@layer components {
.btn-primary {
@apply inline-flex items-center px-5 py-2.5 rounded-full
font-semibold text-white bg-gradient-to-r
from-purple-600 to-emerald-500 hover:opacity-90;
}
}
Guidelines:
- Prefer composition for one-off layouts; extract when the same five-plus utilities repeat across dozens of files.
- Keep @apply shallow — nesting many applied utilities inside other applied utilities recreates the specificity problems Tailwind avoids.
- Framework components — in React or Vue, a
<Button variant="primary">component often beats a global.btn-primaryclass because props document intent. - Do not @apply in loop-generated strings — the scanner only sees static class names in source files; dynamic assembly needs a safelist.
Framework integration
Tailwind pairs naturally with component frameworks:
- React / Next.js — utilities live in JSX; Server Components ship HTML with classes already resolved. See our React and Next.js fundamentals guides.
- Vue — single-file components colocate template classes with script;
@sourcemust include.vuefiles. - Vite / bundlers — import the compiled CSS once in your entry file; HMR reloads utilities on save.
With TypeScript, consider clsx or tailwind-merge to
conditionally join classes without duplicate-conflict bugs (two padding utilities
on the same element).
Worked example: Harbor Supply order dashboard
Harbor Supply's internal ops dashboard lists open purchase orders. A product manager wants a responsive table that collapses to cards on mobile, status badges with accessible contrast, and a sticky filter bar. A Tailwind-first layout:
<div class="min-h-screen bg-slate-50">
<header class="sticky top-0 z-10 border-b bg-white/80 backdrop-blur px-4 py-3">
<div class="mx-auto flex max-w-6xl items-center justify-between gap-4">
<h1 class="text-lg font-semibold text-ink">Open orders</h1>
<input class="w-full max-w-xs rounded-lg border px-3 py-2 text-sm
focus:ring-2 focus:ring-brand/30" placeholder="Filter SKU…" />
</div>
</header>
<main class="mx-auto max-w-6xl p-4 md:p-6">
<!-- Desktop table -->
<table class="hidden w-full text-sm md:table">…</table>
<!-- Mobile cards -->
<ul class="grid gap-4 md:hidden">
<li class="rounded-xl border bg-white p-4 shadow-sm">
<div class="flex items-center justify-between">
<span class="font-mono text-xs text-muted">PO-8842</span>
<span class="rounded-full bg-amber-100 px-2 py-0.5 text-xs
font-medium text-amber-800">Pending</span>
</div>
<p class="mt-2 font-medium">Dock cleats (24 units)</p>
</li>
</ul>
</main>
</div>
Notice the pattern: layout primitives (flex, grid,
max-w-6xl), responsive visibility (hidden md:table),
semantic color tokens (text-ink, text-muted), and
frosted glass on the sticky header (bg-white/80 backdrop-blur) —
all without writing a separate CSS file. Status colors use Tailwind's palette
with sufficient contrast (amber-800 on amber-100) for WCAG AA at small sizes.
Styling approach decision table
| Approach | Best for | Trade-off |
|---|---|---|
| Tailwind utilities | Product UIs, design systems, rapid prototyping, React/Vue apps | Verbose markup; team must learn conventions |
| Plain CSS / SCSS modules | Marketing one-offs, heavy animation, strict BEM teams | Naming and dead-rule drift at scale |
| CSS-in-JS (styled-components) | Theme switching tied to React props, component libraries | Runtime cost; SSR complexity |
| Bootstrap / Bulma | Admin templates, junior teams wanting pre-built components | Harder to customize look; heavier bundles |
| Utility + component hybrid | Large apps with a shared Button/Card kit and Tailwind for layout | Requires discipline on where logic lives |
Common pitfalls
- Arbitrary value abuse —
mt-[17px]everywhere defeats the spacing scale; extend@themeinstead. - Dynamic class strings —
className={'text-' + color}will not be scanned; use a lookup map of full class names. - Missing @source paths — classes in email templates or CMS HTML omitted from scanning disappear in production.
- Over-@apply — giant component classes become harder to maintain than the utilities they replaced.
- Ignoring a11y —
focus:outline-nonewithout afocus-visible:ringreplacement hurts keyboard users. - Purging safelist gaps — CMS-injected class names need explicit safelist entries.
- Mixing conflicting utilities —
p-4 p-6on one element: last class wins in source order, not in HTML attribute order; use merge helpers.
Practitioner checklist
- Define brand colors, fonts, and radii in
@themebefore building pages. - Configure
@sourceto include every template format (HTML, JSX, Vue, MDX). - Run production builds in CI and diff CSS bundle size when adding new dependencies.
- Use responsive prefixes mobile-first; test at 375px and 1280px minimum.
- Extract a component only after the same utility cluster appears three or more times.
- Add
tailwind-merge(or equivalent) wherever classes are composed conditionally. - Verify focus states on every interactive element — never ship invisible focus rings.
- Document custom tokens in a design reference so designers and engineers share vocabulary.
- Pair with Core Web Vitals checks — large blur/backdrop stacks can hurt GPU on low-end phones.
- Keep one authoritative input CSS file; avoid editing compiled output directly.
Key takeaways
- Tailwind encodes a design system as composable utility classes scanned at build time.
- v4 uses
@themeand@sourcein CSS instead of heavy JavaScript configuration. - Responsive and state variants replace most hand-written media queries and pseudo-selectors.
- Prefer inline utilities; extract with
@applyor framework components when repetition demands it. - Production success depends on correct content scanning, accessible focus styles, and token discipline — not memorizing every class name.
Related reading
- CSS fundamentals explained — cascade, box model, and custom properties underneath every utility
- CSS Flexbox and Grid layout explained — the layout primitives Tailwind abbreviates
- Responsive web design explained — mobile-first breakpoints and fluid layout strategy
- React fundamentals explained — component composition patterns that pair well with utility classes