Fix: Tailwind v4 Not Working — @theme, CSS-First Config, PostCSS vs Vite, and v3 Migration
Quick Answer
How to fix Tailwind CSS v4 errors — tailwind.config.js ignored, @import 'tailwindcss' not loading, @theme custom values not applied, content scanning misses files, Vite plugin setup, and v3 to v4 migration gotchas.
The Error
You upgrade to Tailwind v4 and classes stop applying:
<div class="bg-blue-500 p-4">Hello</div>
<!-- No styling. -->Or your tailwind.config.js is completely ignored:
// tailwind.config.js
module.exports = {
theme: {
extend: {
colors: { brand: "#ff0080" },
},
},
};<div class="text-brand">Hello</div>
<!-- Still no color. -->Or the new @theme directive doesn’t work:
@import "tailwindcss";
@theme {
--color-brand: oklch(0.7 0.2 30);
}Or your PostCSS config errors out:
Error: It looks like you're trying to use `tailwindcss` directly as a PostCSS plugin.
The PostCSS plugin has moved to a separate package.Why This Happens
Tailwind v4 is a ground-up rewrite. Three structural changes that break v3 setups:
- CSS-first config.
tailwind.config.jsis no longer the source of truth. Theme tokens live in CSS via@theme { ... }. The JS config still works for migration but is opt-in. - PostCSS plugin moved.
tailwindcssitself is no longer the PostCSS plugin. The plugin is now@tailwindcss/postcss. For Vite users, there’s a dedicated@tailwindcss/viteplugin that’s faster and recommended. @importreplaces@tailwinddirectives.@tailwind base; @tailwind components; @tailwind utilities;becomes a single@import "tailwindcss";.
The “classes don’t apply” issue is usually one of: the PostCSS plugin isn’t wired up, the new import directive is missing, or the content scanner doesn’t see your template files.
Fix 1: Update the PostCSS or Vite Plugin
For Vite projects (Astro, SvelteKit, React with Vite, etc.) — switch to the Vite plugin:
npm install -D tailwindcss @tailwindcss/vite// vite.config.ts
import { defineConfig } from "vite";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [tailwindcss()],
});Remove the old PostCSS config if it only existed for Tailwind. The Vite plugin runs Tailwind directly — no PostCSS round-trip.
For Next.js, Remix, or other PostCSS-driven setups:
npm install -D tailwindcss @tailwindcss/postcss// postcss.config.mjs (or postcss.config.js)
export default {
plugins: {
"@tailwindcss/postcss": {},
},
};Remove tailwindcss from your PostCSS plugins — that’s v3. The plugin moved.
Pro Tip: The Vite plugin is dramatically faster than PostCSS for Tailwind. If your bundler supports it, prefer it.
Fix 2: Use @import "tailwindcss" Once
In your single global CSS file:
@import "tailwindcss";That’s it. No @tailwind base, @tailwind components, @tailwind utilities. The single import pulls in all of Tailwind.
For more control, you can import sub-layers individually:
@import "tailwindcss/preflight";
@import "tailwindcss/utilities";
/* Skipping the components layer */But the recommended pattern is the single import.
Common Mistake: Mixing v3 and v4 directives:
/* WRONG: */
@import "tailwindcss";
@tailwind utilities;
/* Correct: */
@import "tailwindcss";If you copy-pasted from a v3 tutorial, find and replace the old directives.
Fix 3: Define Theme Tokens With @theme
Custom colors, spacing, fonts — everything that used to live in theme.extend — goes in a @theme block:
@import "tailwindcss";
@theme {
--color-brand: oklch(0.7 0.2 30);
--color-brand-soft: oklch(0.85 0.1 30);
--font-display: "Geist", "system-ui", sans-serif;
--spacing-128: 32rem;
--breakpoint-3xl: 1920px;
}These become available as utility classes:
--color-brand→bg-brand,text-brand,border-brand, etc.--font-display→font-display--spacing-128→p-128,m-128,w-128--breakpoint-3xl→3xl:prefix
The naming convention (--color-*, --font-*, --spacing-*, --breakpoint-*) is how Tailwind knows what category each variable belongs to.
Note: Theme variables become real CSS custom properties on :root. You can read them in any CSS too: color: var(--color-brand).
Fix 4: Use the JS Config Compatibility Mode (Migration Path)
To keep your tailwind.config.js working during migration, opt in explicitly:
@import "tailwindcss";
@config "../../tailwind.config.js";The @config directive tells Tailwind to load and merge a v3-style config. This is the bridge while you migrate theme.extend entries into @theme.
Once you’ve moved everything to CSS, delete the config file and remove the @config line.
Common Mistake: Setting up @config and @theme for the same token. The CSS @theme wins. If your JS color isn’t appearing, check whether it’s also defined in @theme.
Fix 5: Content Detection Is Automatic But Has Limits
Tailwind v4 auto-detects template files in your project — no content array needed in most cases. It scans:
- Files in your project (excluding
node_modulesand.gitignored paths by default). - Common template extensions:
.html,.js,.jsx,.ts,.tsx,.vue,.svelte,.astro, etc.
If classes from a particular file aren’t appearing:
@import "tailwindcss";
@source "../node_modules/some-ui-library/dist/**/*.js";@source tells Tailwind to scan extra paths. Use it when:
- A third-party UI library ships pre-built JS with Tailwind classes you want to keep.
- You generate templates outside the default scanned paths.
To explicitly exclude a path:
@source not "./src/legacy/**";Pro Tip: If you migrated from v3 and had a long content: [...] array, replace each entry with @source "...". The behavior is similar.
Fix 6: Dark Mode Configuration
v4 default is prefers-color-scheme. To use a class-based toggle:
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));Now toggling <html class="dark"> switches the theme. This pattern is more flexible than v3’s darkMode: 'class' — you can use any selector, like a data-theme="dark" attribute:
@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));The @custom-variant directive replaces v3’s variant config. Use it for any selector-based variant you need.
Fix 7: Plugins and @plugin
Third-party Tailwind plugins still work but are loaded differently:
@import "tailwindcss";
@plugin "@tailwindcss/typography";
@plugin "@tailwindcss/forms";
@plugin "./my-local-plugin.js";No more plugins: [require('@tailwindcss/typography')] in JS config (unless you’re using @config compatibility).
For first-party plugins, install and reference:
npm install -D @tailwindcss/typography @tailwindcss/formsCustom plugins still use plugin() from tailwindcss/plugin:
// my-local-plugin.js
import plugin from "tailwindcss/plugin";
export default plugin(({ addUtilities }) => {
addUtilities({
".text-shadow": { textShadow: "0 1px 2px rgba(0,0,0,0.1)" },
});
});Fix 8: Migrate From v3 With the Upgrade Tool
The official upgrade tool handles most v3 → v4 conversions:
npx @tailwindcss/upgradeIt:
- Replaces
@tailwinddirectives with@import "tailwindcss". - Converts simple
tailwind.config.jsthemes into@themeblocks in your CSS. - Updates PostCSS config to use
@tailwindcss/postcss. - Removes deprecated utility classes (
flex-grow-0→grow-0, etc.). - Adds
@configfor complex configs it couldn’t auto-migrate.
Run it on a clean git tree and review the diff carefully. Known cases the tool can’t auto-fix:
- Custom plugins that touch internal APIs (the plugin API was simplified — check each plugin).
- Theme tokens that depended on JS functions (e.g. dynamically generated colors).
safelistentries — review and convert to explicit class usage or@sourceincludes.
Common Mistake: Running the upgrade on a project with a half-migrated state. Either commit your v3 state first and run the tool, or finish manual migration before running it.
Still Not Working?
A few less-obvious failures:
oklch()color not rendering on old browsers. v4 uses modern color spaces by default. Browsers older than ~2023 don’t supportoklch. Polyfill with@supportsor use the fallback the Tailwind upgrade tool provides.- Build size is huge after upgrade. v4 ships more by default. Inspect the generated CSS or use the Tailwind CLI’s verbose flag (check
tailwindcss --helpfor your version) to see which files are scanned. Use@source not "..."to exclude generated/test files. - Arbitrary values like
text-[18px]not working. Should still work — confirm the import directive is correct and the file is being scanned. Check for typos in the bracket syntax. @layer base { ... }styles not applying. v4 still supports@layer, but the layer cascade order changed.@layer basestyles come before component utilities; ensure you’re not accidentally overriding them later.- TypeScript types missing for
tailwindcss/plugin. Install@types/tailwindcssif your editor complains, though v4 ships its own types in most setups. - Hot reload not picking up CSS changes. The Vite plugin should handle this. For PostCSS, ensure
@tailwindcss/postcssis fresh enough to support HMR. @applyslower than v3. v4 changed@applyinternals. For hot paths, consider inlining the utility classes directly rather than wrapping in@apply.- Astro/SvelteKit project: classes work in
.astrofiles but not in JS-generated components. Add the JS path with@sourceif it’s outside the auto-scan defaults.
For related CSS and Tailwind issues, see Tailwind classes not applying, CSS tailwind not applying, Vite failed to resolve import, and CSS variable not working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Tailwind CSS Classes Not Applying — Styles Missing in Production or Development
How to fix Tailwind CSS not applying styles — content config paths, JIT mode, dynamic class names, PostCSS setup, CDN vs build tool, and purging issues.
Fix: Mantine Not Working — Styles Not Loading, Theme Not Applying, or Components Broken After Upgrade
How to fix Mantine UI issues — MantineProvider setup, PostCSS configuration, theme customization, dark mode, form validation with useForm, and Next.js App Router integration.
Fix: View Transitions API Not Working — No Animation Between Pages, Cross-Document Transitions Failing, or Fallback Missing
How to fix View Transitions API issues — same-document transitions, cross-document MPA transitions, view-transition-name CSS, Next.js and Astro integration, custom animations, and browser support.
Fix: Panda CSS Not Working — Styles Not Applying, Tokens Not Resolving, or Build Errors
How to fix Panda CSS issues — PostCSS setup, panda.config.ts token system, recipe and pattern definitions, conditional styles, responsive design, and integration with Next.js and Vite.