Migrating from v3 to v4
Purpose
Migrate existing Tailwind CSS v3 projects to v4's CSS-first configuration, updated utilities, and modern color system.
Automated Migration Tool
Tailwind provides an automated upgrade tool:
npx @tailwindcss/upgrade@next
Requirements:
-
Node.js 20 or higher
-
Run in a new git branch
-
Review all changes manually
-
Test thoroughly
What it handles:
-
Updates dependencies
-
Migrates configuration to CSS
-
Updates template files
-
Converts utility class names
What it doesn't handle:
-
Custom plugins (manual migration needed)
-
Complex configuration logic
-
Dynamic class generation
Configuration Migration
JavaScript Config → CSS Theme
v3 (tailwind.config.js):
module.exports = { content: ['./src/**/*.{html,js}'], theme: { extend: { colors: { brand: '#3b82f6', accent: '#a855f7', }, fontFamily: { sans: ['Inter', 'sans-serif'], display: ['Satoshi', 'sans-serif'], }, spacing: { 18: '4.5rem', 72: '18rem', }, borderRadius: { '4xl': '2rem', }, }, }, plugins: [], };
v4 (CSS @theme):
@import 'tailwindcss';
@theme { --font-sans: 'Inter', sans-serif; --font-display: 'Satoshi', sans-serif;
--color-brand: oklch(0.65 0.25 270); --color-accent: oklch(0.65 0.25 320);
--spacing-18: 4.5rem; --spacing-72: 18rem;
--radius-4xl: 2rem; }
Content Detection
v3:
content: ['./src/**/*.{html,js,jsx,ts,tsx}']
v4:
Automatic detection. No configuration needed.
Manual control (if needed):
@import 'tailwindcss'; @source "../packages/ui"; @source not "./legacy";
Import Syntax Changes
v3:
@tailwind base; @tailwind components; @tailwind utilities;
v4:
@import 'tailwindcss';
Utility Class Renames
Opacity Modifiers
v3:
<div class="bg-black bg-opacity-50"></div> <div class="text-gray-900 text-opacity-75"></div> <div class="border-blue-500 border-opacity-60"></div>
v4:
<div class="bg-black/50"></div> <div class="text-gray-900/75"></div> <div class="border-blue-500/60"></div>
Migration pattern:
-
bg-opacity-{value} → bg-{color}/{value}
-
text-opacity-{value} → text-{color}/{value}
-
border-opacity-{value} → border-{color}/{value}
Flex Utilities
v3:
<div class="flex-shrink-0"></div> <div class="flex-shrink"></div> <div class="flex-grow-0"></div> <div class="flex-grow"></div>
v4:
<div class="shrink-0"></div> <div class="shrink"></div> <div class="grow-0"></div> <div class="grow"></div>
Migration pattern:
-
flex-shrink-* → shrink-*
-
flex-grow-* → grow-*
Shadow Utilities
v3:
<div class="shadow-sm"></div>
v4:
<div class="shadow-xs"></div>
Migration:
-
shadow-sm → shadow-xs
-
All other shadow utilities remain the same
Ring Width
v3 default:
<input class="ring" />
Ring width: 3px
v4 default:
<input class="ring" />
Ring width: 1px
To keep v3 behavior:
<input class="ring-3" />
Color System Changes
Default Border and Ring Colors
v3:
<div class="border"></div>
Border color: gray-200
v4:
<div class="border"></div>
Border color: currentColor
To keep v3 behavior:
<div class="border border-gray-200"></div>
OkLCh Color Space
v3 (RGB):
colors: { brand: '#3b82f6', }
v4 (OkLCh):
@theme { --color-brand: oklch(0.65 0.25 270); }
Use conversion tool: https://oklch.com/
PostCSS Configuration
Plugin Changes
v3:
module.exports = { plugins: { 'tailwindcss': {}, 'autoprefixer': {}, }, };
v4:
export default { plugins: { '@tailwindcss/postcss': {}, }, };
No longer need autoprefixer or postcss-import .
Vite Plugin
v3:
import tailwindcss from 'tailwindcss'; import autoprefixer from 'autoprefixer';
export default defineConfig({ css: { postcss: { plugins: [tailwindcss(), autoprefixer()], }, }, });
v4:
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({ plugins: [tailwindcss()], });
Preflight Changes
Placeholder Colors
v3:
Placeholder text: gray-400
v4:
Placeholder text: currentColor at 50% opacity
To keep v3 behavior:
@layer base { ::placeholder { color: theme('colors.gray.400'); } }
Button Cursor
v3:
button { cursor: pointer; }
v4:
button { cursor: default; }
To restore v3 behavior:
@layer base { button { cursor: pointer; } }
Feature Additions
Built-in Container Queries
v3 (plugin required):
plugins: [require('@tailwindcss/container-queries')]
v4 (built-in):
No plugin needed. Use @container and @{breakpoint}: syntax.
3D Transforms
v3:
Not available
v4:
<div class="transform-3d rotate-x-45 rotate-y-30 translate-z-12"></div>
Starting Variant for Animations
v3:
Not available
v4:
<div class="opacity-100 starting:opacity-0 transition-opacity"></div>
Breaking Changes Checklist
-
Update dependencies to v4
-
Migrate tailwind.config.js to @theme
-
Replace @tailwind directives with @import
-
Update PostCSS configuration
-
Convert opacity utilities (bg-opacity → bg-{color}/{value})
-
Rename flex utilities (flex-shrink → shrink)
-
Update shadow-sm to shadow-xs
-
Add explicit border colors if using bare border
-
Update ring-3 if expecting 3px default
-
Convert hex colors to oklch()
-
Remove container-queries plugin (now built-in)
-
Test placeholder colors
-
Test button cursor behavior
-
Update arbitrary value syntax (spaces → underscores)
Migration Strategy
Phase 1: Preparation
-
Create new git branch
-
Ensure all changes committed
-
Run automated migration tool
-
Review generated changes
Phase 2: Configuration
-
Convert tailwind.config.js to CSS @theme
-
Update PostCSS/Vite configuration
-
Replace @tailwind directives
-
Add @source if needed
Phase 3: Utilities
-
Search and replace opacity modifiers
-
Rename flex utilities
-
Update shadow utilities
-
Add explicit border colors
-
Convert hex colors to oklch()
Phase 4: Testing
-
Test all pages/components
-
Verify responsive behavior
-
Check dark mode
-
Test interactive states
-
Validate production build
Phase 5: Cleanup
-
Remove unused dependencies
-
Delete tailwind.config.js
-
Update documentation
-
Commit changes
Common Issues
Styles Not Applying
Check:
-
CSS import: @import "tailwindcss";
-
PostCSS plugin: @tailwindcss/postcss
-
Vite plugin: @tailwindcss/vite
-
Template files not in .gitignore
Class Names Not Working
Ensure:
-
Class names are complete strings
-
Not using dynamic concatenation
-
Using underscores for spaces in arbitrary values
Colors Look Different
OkLCh uses different color space. Convert hex to oklch using: https://oklch.com/
Build Errors
Check:
-
Node.js version (20+)
-
Dependencies updated
-
PostCSS config using correct plugin
See Also
-
references/breaking-changes.md - Complete breaking changes list
-
references/migration-checklist.md - Step-by-step migration guide