React SME Cookbook
All FAQs

Search Documentation

Search across all documentation pages

tailwindv4setupcss-first-configthemeinstall

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 @tailwind directives
  • @theme block for CSS-first configuration (no JS config file)
  • @source for 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
  • @theme defines CSS custom properties that Tailwind converts into utility classes
  • Naming convention: --color-* becomes text-*, bg-*, border-*; --spacing-* becomes p-*, m-*, gap-*
  • @source tells Tailwind where to scan for class names (replaces content in v3 config)
  • Tailwind v4 auto-detects sources in most frameworks — @source is only needed for non-standard paths
  • The @theme block 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.js by 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/utilities is removed — v4 uses @import "tailwindcss" instead. Fix: Replace the three @tailwind directives with a single @import.

  • PostCSS plugin changed — The plugin is now @tailwindcss/postcss, not tailwindcss. Fix: Update your PostCSS config.

  • Content auto-detection — v4 auto-scans your project. If classes are not being detected, add explicit @source paths. Fix: Use @source "../path/**/*.tsx" for non-standard file locations.

  • CSS custom property names@theme values must follow the --category-name pattern to generate correct utilities. --color-brand becomes text-brand, but --brand-color does not.

Alternatives

AlternativeUse WhenDon't Use When
Tailwind v3Legacy project not ready to migrateStarting a new project (v4 is simpler)
CSS ModulesYou need locally-scoped CSS without utility classesYou want rapid prototyping with utilities
Vanilla ExtractYou want type-safe CSS-in-TS with zero runtimeYou prefer utility-first development
UnoCSSYou want an ultra-fast, configurable utility engineYou 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-brand becomes text-brand, bg-brand, border-brand
  • --spacing-18 becomes p-18, m-18, gap-18
  • --font-family-sans becomes font-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";