Breaking down the new CSS-first engine, the PostCSS plugin split, and the real migration gotchas from v3 to v4.
March 31, 2026

Tailwind v4 is a ground-up rewrite. The utility class names are mostly the same, the design philosophy is the same, but the internals — how it processes CSS, how you configure it, how it integrates with your build — changed significantly. If you're upgrading from v3, the familiar surface hides some real breaking changes.
In v3, configuration lived in tailwind.config.js. Custom colors, fonts, breakpoints, plugins — all JavaScript.
In v4, configuration is CSS-first. You configure Tailwind by writing CSS:
/* v4 — in your main CSS file */
@import "tailwindcss";
@theme {
--color-brand: #0ea5e9;
--color-brand-dark: #0284c7;
--font-sans: "Inter", sans-serif;
--breakpoint-3xl: 1920px;
}
These CSS variables become Tailwind utilities automatically. --color-brand becomes bg-brand, text-brand, border-brand. --breakpoint-3xl becomes the 3xl: variant.
The tailwind.config.js file still works for backwards compatibility, but the CSS-first approach is the intended path forward.
In v3, tailwindcss was both the core library and the PostCSS plugin. One package handled everything.
In v4, these are separate packages:
# v3
pnpm add -D tailwindcss postcss autoprefixer
# v4
pnpm add -D tailwindcss @tailwindcss/postcss
Your postcss.config.mjs:
// v3
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
// v4
export default {
plugins: {
'@tailwindcss/postcss': {},
},
}
Note that autoprefixer is no longer needed — v4 handles vendor prefixing internally.
@import SyntaxIn v3, you used @tailwind directives:
/* v3 */
@tailwind base;
@tailwind components;
@tailwind utilities;
In v4, it's a single import:
/* v4 */
@import "tailwindcss";
This also means you can import Tailwind as part of a standard CSS cascade, which makes it easier to control ordering with your own reset or base styles.
content Configv3 required you to list every file path that might contain Tailwind classes:
// v3 tailwind.config.js
module.exports = {
content: [
'./src/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
}
v4 uses automatic content detection. It scans your project for files containing utility classes without any configuration. For most projects, this just works. Edge cases (e.g., classes in JSON files, dynamically constructed class strings) may still need manual configuration via @source directives:
@import "tailwindcss";
@source "../node_modules/my-ui-library"; /* explicit extra source */
Most utility classes are identical. flex, grid, p-4, text-lg, hover:bg-blue-500 — if it worked in v3 it works in v4.
The variants system (hover:, focus:, dark:, responsive prefixes) works the same way.
The @apply directive still works in v4 for applying utilities in component classes:
.btn-primary {
@apply bg-brand text-white px-4 py-2 rounded-lg;
}
ring utilities changed. ring used to add a 3px ring by default. In v4 it defaults to 1px. If you relied on ring for focus styles, check your designs.
Opacity modifiers tightened. bg-blue-500/50 still works, but some edge cases with the old opacity plugin behavior changed.
theme() function. If you used theme('colors.blue.500') in custom CSS, you'll need to migrate to CSS variables: var(--color-blue-500).
JIT is the only mode. v3 had both JIT (default) and classic mode. v4 is JIT-only. If you had any workarounds for non-JIT behavior, remove them.
For greenfield projects: yes. The CSS-first config is cleaner, automatic content detection removes maintenance overhead, and the performance is faster.
For existing v3 projects: the migration is mechanical but requires attention. The automatic upgrade tool (npx @tailwindcss/upgrade) handles most of it, but review the ring and opacity changes manually.
If your project has a lot of custom plugins written for v3 the JavaScript API, check plugin compatibility before upgrading — the plugin API changed.
tailwind.config.js to CSS @theme variables — leaner and more composable@tailwindcss/postcss replaces tailwindcss as the PostCSS plugin; autoprefixer is no longer needed@import "tailwindcss" replaces the three @tailwind directivescontent array in config for standard setupsring defaults, opacity edge cases, and the theme() function