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