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
@themevariables inside nested selectors. Right: Must be top-level; Tailwind validates this. - Wrong: Using
var(--font-inter)inside@themewithoutinlinemodifier. Right:@theme inline { --font-sans: var(--font-inter) }. - Wrong: Confusing
--spacing(base multiplier) with--spacing-18(named token). They are different. - Wrong: Adding
!importantto make@themevalues override defaults. Right: Unnecessary — Tailwind uses@theme defaultfor built-ins, which user tokens automatically override.
See Also
- v3 Configuration
- Design Token Mapping
- Reference: https://tailwindcss.com/docs/theme
- Reference: https://tailwindcss.com/docs/adding-custom-styles