Tailwind CSS v4 Setup
Install Tailwind CSS v4, configure with CSS-first approach, set up @theme, and use @import "tailwindcss".
Recipe
Quick-reference recipe card — copy-paste ready.
# Install Tailwind CSS v4 with Next.js
npm install tailwindcss @tailwindcss/postcss postcss
# Or with Vite
npm install tailwindcss @tailwindcss/vite/* app/globals.css — the ONLY config you need */
@import "tailwindcss";
/* Custom theme values */
@theme {
--color-brand: #3b82f6;
--color-brand-dark: #1d4ed8;
--font-family-heading: "Inter", sans-serif;
--breakpoint-3xl: 1920px;
}// postcss.config.mjs (Next.js)
export default {
plugins: {
"@tailwindcss/postcss": {},
},
};// vite.config.ts (Vite)
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [tailwindcss()],
});When to reach for this: At the start of every new project using Tailwind CSS v4 — the setup is simpler than v3, with no tailwind.config.js needed.
Working Example
/* app/globals.css — full project setup */
@import "tailwindcss";
/* Source detection — tell Tailwind where your classes are */
@source "../components/**/*.tsx";
@source "../lib/**/*.ts";
/* Custom theme */
@theme {
/* Colors */
--color-primary: #2563eb;
--color-primary-foreground: #ffffff;
--color-secondary: #64748b;
--color-destructive: #ef4444;
--color-muted: #f1f5f9;
--color-border: #e2e8f0;
/* Spacing */
--spacing-18: 4.5rem;
--spacing-128: 32rem;
/* Typography */
--font-family-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
--font-family-mono: "JetBrains Mono", ui-monospace, monospace;
/* Border radius */
--radius-DEFAULT: 0.5rem;
--radius-lg: 0.75rem;
/* Shadows */
--shadow-soft: 0 2px 8px rgb(0 0 0 / 0.08);
/* Animations */
--animate-fade-in: fade-in 0.3s ease-out;
}
@keyframes fade-in {
from { opacity: 0; transform: translateY(-4px); }
to { opacity: 1; transform: translateY(0); }
}
/* Custom utilities */
@utility container-narrow {
max-width: 42rem;
margin-inline: auto;
padding-inline: 1rem;
}
/* Custom variant */
@variant hocus (&:hover, &:focus-visible);
/* Layer overrides */
@layer base {
html {
font-family: var(--font-family-sans);
color: var(--color-foreground, #0f172a);
}
h1, h2, h3 {
font-family: var(--font-family-heading, var(--font-family-sans));
}
}// Usage in components
export function Hero() {
return (
<section className="container-narrow animate-fade-in py-18">
<h1 className="text-4xl font-bold text-primary">Welcome</h1>
<p className="mt-4 text-secondary">Build something great.</p>
<button className="mt-6 rounded-lg bg-primary px-6 py-3 text-primary-foreground shadow-soft hocus:bg-primary-dark">
Get Started
</button>
</section>
);
}What this demonstrates:
@import "tailwindcss"replaces the old@tailwinddirectives@themeblock for CSS-first configuration (no JS config file)@sourcefor explicit content detection- Custom utilities with
@utility - Custom variants with
@variant - Theme values used as utility classes (
text-primary,shadow-soft, etc.)
Deep Dive
How It Works
- Tailwind v4 is CSS-first — all configuration lives in your CSS file, not a JS config
@import "tailwindcss"includes all of Tailwind's base, components, and utilities layers@themedefines CSS custom properties that Tailwind converts into utility classes- Naming convention:
--color-*becomestext-*,bg-*,border-*;--spacing-*becomesp-*,m-*,gap-* @sourcetells Tailwind where to scan for class names (replacescontentin v3 config)- Tailwind v4 auto-detects sources in most frameworks —
@sourceis only needed for non-standard paths - The
@themeblock compiles to CSS custom properties, enabling runtime theming
Variations
Migrating from v3 config:
# Automatic migration tool
npx @tailwindcss/upgrade/* v3 tailwind.config.js colors → v4 @theme */
/* Before (JS): colors: { brand: { 500: '#3b82f6' } } */
/* After (CSS): */
@theme {
--color-brand-500: #3b82f6;
}Disabling default theme values:
@theme {
/* Remove all default colors */
--color-*: initial;
/* Define only your colors */
--color-primary: #2563eb;
--color-gray-50: #f9fafb;
--color-gray-900: #111827;
}Multiple CSS files:
/* styles/theme.css */
@theme {
--color-brand: #3b82f6;
}
/* app/globals.css */
@import "tailwindcss";
@import "./styles/theme.css";TypeScript Notes
// No TypeScript impact — Tailwind is CSS-only
// But you can type your theme tokens for consistency:
const themeColors = {
primary: "text-primary",
secondary: "text-secondary",
destructive: "text-destructive",
} as const;
type ThemeColor = keyof typeof themeColors;Gotchas
-
No
tailwind.config.jsby default — v4 does not read a JS config file. Fix: All config goes in CSS via@theme. If you must use JS config (for plugins), use@config "./tailwind.config.js"in your CSS. -
@tailwind base/components/utilitiesis removed — v4 uses@import "tailwindcss"instead. Fix: Replace the three@tailwinddirectives with a single@import. -
PostCSS plugin changed — The plugin is now
@tailwindcss/postcss, nottailwindcss. Fix: Update your PostCSS config. -
Content auto-detection — v4 auto-scans your project. If classes are not being detected, add explicit
@sourcepaths. Fix: Use@source "../path/**/*.tsx"for non-standard file locations. -
CSS custom property names —
@themevalues must follow the--category-namepattern to generate correct utilities.--color-brandbecomestext-brand, but--brand-colordoes not.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Tailwind v3 | Legacy project not ready to migrate | Starting a new project (v4 is simpler) |
| CSS Modules | You need locally-scoped CSS without utility classes | You want rapid prototyping with utilities |
| Vanilla Extract | You want type-safe CSS-in-TS with zero runtime | You prefer utility-first development |
| UnoCSS | You want an ultra-fast, configurable utility engine | You want Tailwind's ecosystem and community |
FAQs
What single import replaces the three @tailwind directives from v3?
@import "tailwindcss" replaces @tailwind base, @tailwind components, and @tailwind utilities.
Where does all configuration live in Tailwind CSS v4?
In your CSS file inside @theme { } blocks. There is no tailwind.config.js by default.
How does the @theme naming convention map to utility classes?
--color-brandbecomestext-brand,bg-brand,border-brand--spacing-18becomesp-18,m-18,gap-18--font-family-sansbecomesfont-sans
When do you need to use @source in v4?
Only when Tailwind's auto-detection misses your files. Add @source "../path/**/*.tsx" for non-standard file locations.
How do you migrate an existing v3 project to v4?
Run npx @tailwindcss/upgrade. It converts your tailwind.config.js values into @theme CSS blocks.
How do you remove all default colors and define only your own?
@theme {
--color-*: initial;
--color-primary: #2563eb;
}What is the correct PostCSS plugin name for v4?
@tailwindcss/postcss, not tailwindcss. Using the old name will fail silently.
Gotcha: Why does --brand-color not generate any utility classes?
Theme values must follow the --category-name pattern. --color-brand works, but --brand-color does not match any utility category.
Gotcha: You added @theme values but still see a tailwind.config.js — will they conflict?
They can. Use @config "./tailwind.config.js" in CSS only when you need JS-based plugins. Move all theme values to @theme to avoid conflicts.
How can you type-check theme token usage in TypeScript?
const themeColors = {
primary: "text-primary",
secondary: "text-secondary",
} as const;
type ThemeColor = keyof typeof themeColors;Tailwind itself is CSS-only, but you can create a typed lookup map for consistency.
How do you define a custom utility and a custom variant in v4?
@utility container-narrow {
max-width: 42rem;
margin-inline: auto;
}
@variant hocus (&:hover, &:focus-visible);Can you split @theme across multiple CSS files?
Yes. Import a separate theme file after @import "tailwindcss":
@import "tailwindcss";
@import "./styles/theme.css";Related
- Utilities — core utility class cheatsheet
- Dark Mode — dark mode and theming
- Custom Utilities — @utility, @variant, plugins
- shadcn Setup — shadcn + Tailwind integration