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-summary or .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 old content array).
  • @theme — extends or overrides the default design token map; custom colors become utilities like bg-brand automatically.
  • Standalone CLI./bin/tailwindcss -i src/input.css -o dist/style.css --minify with no PostCSS required for many projects.
  • Layer control@layer theme, components, utilities orders 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-primary class 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; @source must include .vue files.
  • 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 abusemt-[17px] everywhere defeats the spacing scale; extend @theme instead.
  • Dynamic class stringsclassName={'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 a11yfocus:outline-none without a focus-visible:ring replacement hurts keyboard users.
  • Purging safelist gaps — CMS-injected class names need explicit safelist entries.
  • Mixing conflicting utilitiesp-4 p-6 on 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 @theme before building pages.
  • Configure @source to 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 @theme and @source in CSS instead of heavy JavaScript configuration.
  • Responsive and state variants replace most hand-written media queries and pseudo-selectors.
  • Prefer inline utilities; extract with @apply or 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