Overview
Tailwind CSS v4 uses CSS variables (custom properties) for theming, enabling dynamic theme switching, dark mode, and customization without rebuilding your CSS.
Theme Architecture
Tailwind v4’s theme system is built on CSS variables defined in the :root selector.
Basic Theme Structure
@theme {
/* Colors */
--color-primary: #3b82f6;
--color-secondary: #8b5cf6;
--color-accent: #f59e0b;
/* Spacing */
--spacing: 0.25rem;
/* Typography */
--font-sans: system-ui, sans-serif;
--text-base: 1rem;
--text-base--line-height: 1.5rem;
}
This generates:
:root, :host {
--color-primary: #3b82f6;
--color-secondary: #8b5cf6;
--color-accent: #f59e0b;
--spacing: .25rem;
--font-sans: system-ui, sans-serif;
--text-base: 1rem;
--text-base--line-height: 1.5rem;
}
Tailwind applies theme variables to both :root and :host to support Web Components.
Reference Mode
Use @theme reference for values that shouldn’t be output as CSS variables:
@theme reference {
--breakpoint-sm: 640px;
--breakpoint-md: 768px;
--breakpoint-lg: 1024px;
--breakpoint-xl: 1280px;
--breakpoint-2xl: 1536px;
}
These values are used in calculations and media queries but aren’t emitted as CSS variables:
/* No CSS variables generated */
/* Values are inlined where needed */
@media (min-width: 768px) {
/* ... */
}
Use reference mode for:
- Breakpoints (used in media queries)
- Intermediate calculation values
- Legacy compatibility values
- Values only used in build-time calculations
Theme Namespaces
Theme values are organized by namespace prefixes:
Color Namespace
@theme {
/* Blue palette */
--color-blue-50: #eff6ff;
--color-blue-100: #dbeafe;
--color-blue-200: #bfdbfe;
--color-blue-500: #3b82f6;
--color-blue-900: #1e3a8a;
/* Semantic colors */
--color-primary: var(--color-blue-500);
--color-danger: #ef4444;
--color-success: #10b981;
}
Utilities look up colors with the --color- prefix:
<div class="bg-blue-500 text-primary">
<!-- Resolves --color-blue-500 and --color-primary -->
</div>
Spacing Namespace
@theme {
--spacing: 0.25rem;
/* Specific spacing values */
--spacing-xs: 0.5rem;
--spacing-sm: 0.75rem;
--spacing-md: 1rem;
--spacing-lg: 1.5rem;
--spacing-xl: 2rem;
}
The base --spacing value is used for bare number utilities:
<div class="p-4">
<!-- 4 * var(--spacing) = 4 * 0.25rem = 1rem -->
</div>
<div class="m-lg">
<!-- Uses var(--spacing-lg) = 1.5rem -->
</div>
Typography Namespace
@theme {
/* Font sizes */
--text-xs: 0.75rem;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
--text-xl: 1.25rem;
/* Line heights */
--text-xs--line-height: 1rem;
--text-sm--line-height: 1.25rem;
--text-base--line-height: 1.5rem;
/* Font families */
--font-sans: system-ui, sans-serif;
--font-serif: 'Georgia', serif;
--font-mono: 'Courier New', monospace;
}
Other Namespaces
@theme {
/* Breakpoints */
--breakpoint-sm: 640px;
--breakpoint-md: 768px;
/* Border radius */
--radius: 0.25rem;
--radius-lg: 0.5rem;
--radius-full: 9999px;
/* Shadows */
--shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
/* Z-index */
--z-index-dropdown: 1000;
--z-index-modal: 1050;
--z-index-tooltip: 1100;
/* Container widths */
--container-sm: 640px;
--container-md: 768px;
--container-lg: 1024px;
}
Dynamic Theme Switching
Because Tailwind v4 uses CSS variables, you can switch themes dynamically with JavaScript or additional CSS.
Multiple Color Schemes
@theme {
/* Default (light) theme */
--color-background: white;
--color-text: #1f2937;
--color-border: #e5e7eb;
}
/* Dark theme */
:root[data-theme="dark"] {
--color-background: #1f2937;
--color-text: white;
--color-border: #374151;
}
/* High contrast theme */
:root[data-theme="high-contrast"] {
--color-background: black;
--color-text: white;
--color-border: white;
}
<div class="bg-[var(--color-background)] text-[var(--color-text)]">
Content adapts to the active theme
</div>
// Switch themes
document.documentElement.setAttribute('data-theme', 'dark');
Component-Level Themes
@theme {
--button-bg: var(--color-blue-500);
--button-text: white;
}
.theme-danger {
--button-bg: var(--color-red-500);
}
.theme-success {
--button-bg: var(--color-green-500);
}
<div class="theme-danger">
<button class="bg-[var(--button-bg)] text-[var(--button-text)]">
Danger Button
</button>
</div>
Dark Mode
Tailwind v4’s dark mode is implemented using CSS variables and the dark: variant.
Default Dark Mode
The default implementation uses prefers-color-scheme:
/* From variants.ts:1143 */
staticVariant('dark', ['@media (prefers-color-scheme: dark)'])
Usage:
<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-white">
Automatically adapts to system preference
</div>
Custom Dark Mode Implementation
Override the dark variant for class-based dark mode:
@custom-variant dark (&:is([data-theme='dark'] *));
Or using a class:
@custom-variant dark (&:is(.dark *));
<html class="dark">
<body>
<div class="bg-white dark:bg-gray-900">
Controlled by .dark class
</div>
</body>
</html>
Per-Component Dark Mode
@theme {
--card-bg: white;
--card-text: #1f2937;
}
@media (prefers-color-scheme: dark) {
:root {
--card-bg: #1f2937;
--card-text: white;
}
}
<div class="bg-[var(--card-bg)] text-[var(--card-text)]">
Card content
</div>
Semantic Color Systems
Create semantic color systems that can be themed:
@theme {
/* Brand colors */
--color-brand-primary: #3b82f6;
--color-brand-secondary: #8b5cf6;
/* Semantic colors */
--color-success: #10b981;
--color-warning: #f59e0b;
--color-error: #ef4444;
--color-info: #3b82f6;
/* Surface colors */
--color-surface-base: white;
--color-surface-raised: #f9fafb;
--color-surface-overlay: white;
/* Text colors */
--color-text-primary: #1f2937;
--color-text-secondary: #6b7280;
--color-text-disabled: #9ca3af;
/* Border colors */
--color-border-default: #e5e7eb;
--color-border-strong: #d1d5db;
}
<button class="bg-success text-white">
Success Button
</button>
<div class="bg-surface-raised border-border-default">
Raised surface
</div>
Scoped Themes
Create themes scoped to specific sections:
@theme {
--theme-primary: #3b82f6;
--theme-secondary: #8b5cf6;
}
.theme-marketing {
--theme-primary: #f59e0b;
--theme-secondary: #ef4444;
}
.theme-dashboard {
--theme-primary: #10b981;
--theme-secondary: #06b6d4;
}
<div class="theme-marketing">
<button class="bg-[var(--theme-primary)]">
Marketing CTA
</button>
</div>
<div class="theme-dashboard">
<button class="bg-[var(--theme-primary)]">
Dashboard Action
</button>
</div>
Theme Resolution
Understanding how Tailwind resolves theme values:
Resolution Order
From theme.ts, theme resolution follows this order:
- Check the exact key in the specified namespace
- Check bare key (without namespace prefix)
- Return null if not found
// Simplified from theme.ts
resolve(key: string | null, namespaces: ThemeKey[]): string | null {
for (let namespace of namespaces) {
// Try namespace + key
let value = this.get([namespace, key])
if (value !== null) return value
}
// Try bare key
if (key === null) {
for (let namespace of namespaces) {
let value = this.get([namespace])
if (value !== null) return value
}
}
return null
}
Inline vs. Variable Resolution
/* Variable resolution (default) */
.text-blue-500 {
color: var(--color-blue-500);
}
/* Inline resolution (in media queries) */
@media (width >= theme(--breakpoint-md)) {
/* Value is inlined: @media (width >= 768px) */
}
Theme Functions
Access theme values using the theme() function:
.custom-component {
/* Access theme value as CSS variable */
padding: theme(--spacing);
/* Force inline resolution */
padding: theme(--spacing inline);
/* With fallback */
color: theme(--color-primary, blue);
}
The theme() function only works with CSS variable names (prefixed with --). It cannot access nested dot-notation paths from v3.
CSS variables have excellent performance characteristics:
- Variables are resolved at runtime (no recompilation needed)
- Inheritance is efficient (browsers optimize this)
- Theme switching is instant (just update the variable value)
- No JavaScript required for theme switching
When to Use Reference Mode
Use @theme reference to:
- Reduce CSS output size
- Avoid polluting the global CSS variable scope
- Prevent values from being overridden
- Keep intermediate calculation values private
/* These values don't need to be CSS variables */
@theme reference {
--breakpoint-sm: 640px;
--spacing-multiplier: 0.25;
--golden-ratio: 1.618;
}
Migration from v3
Config to @theme
Tailwind v3 config:
// tailwind.config.js (v3)
module.exports = {
theme: {
colors: {
primary: '#3b82f6',
secondary: '#8b5cf6',
},
spacing: {
xs: '0.5rem',
sm: '0.75rem',
}
}
}
Tailwind v4 theme:
@theme {
--color-primary: #3b82f6;
--color-secondary: #8b5cf6;
--spacing-xs: 0.5rem;
--spacing-sm: 0.75rem;
}
Accessing Theme in CSS
v3 approach:
/* v3 - theme() function with dot notation */
.custom {
color: theme('colors.blue.500');
}
v4 approach:
/* v4 - CSS variables or theme() with -- prefix */
.custom {
color: var(--color-blue-500);
/* or */
color: theme(--color-blue-500);
}
Best Practices
Recommendations:
- Semantic Naming: Use semantic names (—color-primary) over specific names (—color-blue-500)
- Consistent Prefixes: Follow Tailwind’s namespace conventions (—color-, —spacing-, etc.)
- Reference Mode: Use for values that don’t need to be CSS variables
- Theme Composition: Build complex themes by referencing simpler ones
- Documentation: Document your theme structure for team members
- Testing: Test theme switching across all components
Examples
Complete Multi-Theme System
@theme {
/* Base variables */
--spacing: 0.25rem;
--font-sans: system-ui, sans-serif;
/* Light theme (default) */
--color-background: white;
--color-surface: #f9fafb;
--color-text: #1f2937;
--color-text-muted: #6b7280;
--color-border: #e5e7eb;
--color-primary: #3b82f6;
}
/* Dark theme */
@media (prefers-color-scheme: dark) {
:root {
--color-background: #0f172a;
--color-surface: #1e293b;
--color-text: white;
--color-text-muted: #94a3b8;
--color-border: #334155;
--color-primary: #60a5fa;
}
}
/* High contrast theme */
:root[data-theme="high-contrast"] {
--color-background: black;
--color-surface: #1a1a1a;
--color-text: white;
--color-text-muted: #e5e5e5;
--color-border: white;
--color-primary: #00ff00;
}
Source Code Reference
The theming system is implemented in:
/packages/tailwindcss/src/theme.ts - Theme class and resolution
/packages/tailwindcss/src/css-functions.ts:68-127 - theme() function
/packages/tailwindcss/src/variants.ts:1143 - dark variant