Skip to content

v4 Configuration: CSS-First

When to Use

Use when configuring a Tailwind v4 project. All configuration lives in CSS via @theme — no JS config file required.

Decision

If you need... Use... Why
Add new tokens (colors, fonts, etc.) @theme { --color-*: value } Extends defaults; generates utility classes automatically
Remove all default colors, use only yours @theme { --color-*: initial; --color-brand: ... } Namespace reset prevents default utilities from generating
Override a single default breakpoint @theme { --breakpoint-sm: 30rem } Replaces only that token; others remain
Wipe ALL defaults, full custom theme @theme { --*: initial; ... } Complete blank slate
Reference design token in CSS var(--color-brand) All @theme variables are exposed as CSS custom properties
Load v3 JS config (gradual migration) @config "../../tailwind.config.js" Bridges v3 config; not all v3 keys are supported

Pattern

@import "tailwindcss";

/* Extend defaults — add tokens without removing defaults */
@theme {
  --color-brand-500: oklch(0.65 0.18 260);
  --color-brand-600: oklch(0.55 0.20 260);
  --font-display: "Inter", "sans-serif";
  --breakpoint-3xl: 120rem;
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* Custom utilities (v4 replaces @layer utilities) */
@utility content-auto {
  content-visibility: auto;
}

/* Custom variant */
@custom-variant theme-dark (&:where([data-theme="dark"], [data-theme="dark"] *));

/* Additional source paths (if auto-detection misses them) */
@source "../node_modules/@company/ui-lib";

@theme Namespace Reference

Namespace Generated utilities Example token
--color-* bg-, text-, border-, ring-, fill-, stroke- --color-brand: oklch(...)
--font-* font-family utilities --font-display: "Inter"
--text-* text-size utilities; paired --text-*--line-height sets line-height --text-xl: 1.25rem
--font-weight-* font-weight utilities --font-weight-semibold: 600
--tracking-* tracking- (letter-spacing) utilities --tracking-wide: 0.025em
--leading-* leading- (line-height) utilities --leading-relaxed: 1.625
--spacing Base multiplier for ALL spacing utilities --spacing: 0.25rem (4px)
--spacing-* Named spacing tokens --spacing-18: 4.5rem
--radius-* rounded- utilities --radius-xs: 0.125rem
--shadow-* shadow- utilities --shadow-sm, --shadow-2xl
--inset-shadow-* inset-shadow- utilities --inset-shadow-sm
--drop-shadow-* drop-shadow- filter utilities --drop-shadow-md
--text-shadow-* text-shadow- utilities (new in v4) --text-shadow-sm
--blur-* blur- utilities --blur-xs: 4px
--breakpoint-* Responsive variants (sm:, lg:, etc.) --breakpoint-3xl: 120rem
--container-* Container query size tokens (@sm:, @lg:, etc.) --container-sm: 24rem
--animate-* animate- utilities; @keyframes live inside @theme --animate-slide-in: slide-in 0.3s
--ease-* ease- utilities --ease-in: cubic-bezier(0.4, 0, 1, 1)
--perspective-* perspective- utilities (new in v4) --perspective-normal: 500px
--aspect-* aspect- utilities --aspect-video: 16 / 9

Next.js Font Integration

/* globals.css — pattern generated by create-next-app */
@import "tailwindcss";

@theme inline {
  /* Next.js font variables → Tailwind font utilities */
  --font-sans: var(--font-geist-sans);
  --font-mono: var(--font-geist-mono);
}
/* layout.tsx */
import { Geist } from 'next/font/google';
const geistSans = Geist({ variable: '--font-geist-sans', subsets: ['latin'] });

export default function RootLayout({ children }) {
  return (
    <html>
      <body className={`${geistSans.variable} antialiased`}>{children}</body>
    </html>
  );
}

The @theme inline modifier forces Tailwind to resolve the CSS variable value at utility generation time rather than creating a var() reference at runtime.

Content Detection (@source)

@source "../node_modules/@company/ui-lib"; /* external packages */
@source not "../src/legacy";               /* ignore paths */
@import "tailwindcss" source("../src");    /* restrict base path */

/* Force-generate specific classes (safelisting) */
@source inline("hover:bg-brand-{500,600}");

Common Mistakes

  • Wrong: Defining @theme variables inside nested selectors. Right: Must be top-level; Tailwind validates this.
  • Wrong: Using var(--font-inter) inside @theme without inline modifier. Right: @theme inline { --font-sans: var(--font-inter) }.
  • Wrong: Confusing --spacing (base multiplier) with --spacing-18 (named token). They are different.
  • Wrong: Adding !important to make @theme values override defaults. Right: Unnecessary — Tailwind uses @theme default for built-ins, which user tokens automatically override.

See Also