JSTAcademy
0 XP
Dashboard
Crash Courses
Tailwind v4 & Production
10 min
PhD+200 XP
Crash Courses · PhD

Tailwind v4 & Production

Understand Tailwind v4 CSS-first config and production optimization.
10 min read+200 XP on completionCert: Tailwind CSS Crash Course
Tap any word in the text below to start reading from there.

Tailwind v4 & Production

Tailwind v4 shifts to CSS-first configuration. The JIT engine is rewritten in Rust via Lightning CSS â€" significantly faster builds.

Tailwind v4: CSS Config

/* app/globals.css */
@import "tailwindcss";

@theme {
  --color-brand-50: #fffbeb;
  --color-brand-500: #f59e0b;
  --color-brand-700: #b45309;
  --font-sans: var(--font-inter), system-ui, sans-serif;
  --radius-4xl: 2rem;
  --animate-fade-in: fadeIn 0.3s ease-out;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

No tailwind.config.ts needed for basic theming. Custom tokens become utilities: bg-brand-500, text-brand-700.

v3 vs v4 Key Differences

v3                          v4
tailwind.config.ts          @theme in CSS
content: [...]              auto-detected
PostCSS                     Lightning CSS (faster)
darkMode: 'class'           @variant dark (&:where(.dark, .dark *))
require() plugins           @plugin directives

twMerge for Class Conflicts

import { twMerge } from 'tailwind-merge'
import { clsx, type ClassValue } from 'clsx'

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

// Without twMerge: both px-4 and px-6 would be in the class string (conflict)
// With twMerge: the LAST one wins correctly
<Button className="px-6">  // overrides Button's default px-4

Production Checklist

✅ All content paths in tailwind.config.ts content array (v3)
✅ No dynamic class construction: bg-${color}-500 (JIT can't scan)
✅ Safelist for dynamic classes: safelist: ['bg-red-500', 'bg-green-500']
✅ Purge working: check final CSS size (should be under 20KB)
✅ twMerge in cn() utility for overrideable components
✅ No @apply for anything you can do with a React component
0%