Skip to main content
CodeAlchemy
Tailwind

Tailwind v4 + Next.js: what actually changed for app router projects

CodeAlchemy Team··7 min read

Source: Malahim's blog

Tailwind

Release notes tell you what changed. Framework-shaped write-ups answer the question every Next.js team actually asks: "what breaks in our repo, and what is just different?". Malahim's walkthrough is the right kind of community piece for that — opinionated, concrete, and willing to call out the rough edges.

Great for teams asking: "What breaks in our Next.js repo, and what is just different?"

TL;DR for Next.js teams

  • No more `tailwind.config.ts` for most projects — @theme blocks in your global CSS replace it.
  • `@tailwind base/components/utilities` is gone — one line: @import "tailwindcss".
  • `postcss.config.js` shrinks to the v4 PostCSS plugin only; postcss-import and autoprefixer are no longer required.
  • Source detection finds your app/, pages/, components/, and src/ automatically. Monorepos still need an @source line per workspace.
  • Visual regressions are real — the OKLCH palette is slightly different from the v3 sRGB defaults; budget a design review pass.

The PostCSS path (still the default in Next.js)

Next.js uses PostCSS under the hood. v4 ships a first-party plugin that replaces the old tailwindcss PostCSS entry plus its companions:

// postcss.config.mjs (Next.js, v4)
export default {
  plugins: {
    "@tailwindcss/postcss": {},
  },
};

Compare to a typical v3 file with three or four entries (postcss-import, tailwindcss/nesting, tailwindcss, autoprefixer). Lightning CSS handles @import, vendor prefixes, and modern syntax internally, so the chain collapses.

If you previously customized the chain (CSS modules tweaks, custom nesting rules), keep those plugins; the v4 plugin coexists with the rest of the chain.

The Vite path (for app-router projects on a non-Next runtime)

The @tailwindcss/vite plugin is the recommended path if you're on Astro, Remix's Vite runtime, or a fresh React + Vite app. Drop it in:

// vite.config.ts
import tailwindcss from "@tailwindcss/vite";

export default {
  plugins: [tailwindcss()],
};

The mental model is the same as PostCSS — same @theme, same @source rules, same output — but the integration is tighter because Vite hands the plugin a richer file watcher.

For Next.js itself, keep the PostCSS path. The Next team's recommended pipeline is well-tested with the PostCSS plugin and there's no current advantage to detouring through Vite-specific tooling.

Where `@import "tailwindcss"` goes

In Next.js apps, your global stylesheet is usually app/globals.css (App Router) or styles/globals.css (Pages Router). The v4 entry is one line:

/* app/globals.css */
@import "tailwindcss";

@theme {
  --color-brand-500: oklch(0.70 0.22 142);
  --font-display: "Inter Display", ui-sans-serif, system-ui;
  --radius-card: 0.75rem;
}

/* Your app-level overrides — variables, base styles, anything custom */
:root {
  --bg: oklch(0.98 0.005 145);
}

Two things to watch:

  • The @import must be before your @theme block, since the theme namespaces are defined by the import.
  • Don't double-import in component CSS modules — that produces duplicate utilities and inflates the bundle.

Source detection in a Next.js layout

The compiler walks the project automatically. For a typical Next.js app, that's exactly what you want — app/**, components/**, src/** are all scanned without configuration.

Three places where you still need @source:

  • Pulling classes out of a sibling package in a monorepo:
  @source "../../packages/ui/dist/**/*.{js,mjs}";
  • Excluding a folder you don't want scanned (e.g. legacy templates being phased out):
  @source not "../legacy-pages/**";
  • Safelisting runtime-only strings that don't appear as literals in source:
  @source inline("bg-success-500 bg-warning-500 bg-danger-500");

If you build a class name dynamically from a database column (status colors, theme keys), this is the cleanest pattern — better than the old safelist array.

What disappears from your codebase

After the upgrade, expect these files to either shrink or vanish:

  • `tailwind.config.ts` — most projects no longer need it. Keep only if you're using third-party plugins like @tailwindcss/forms or @tailwindcss/typography.
  • `postcss.config.mjs` — usually one plugin entry left.
  • Type importsimport type { Config } from "tailwindcss" is no longer relevant for the theme path.

What stays:

  • Plugin imports for forms, typography, and any third-party plugin you rely on.
  • `globals.css` — gains a @theme block, loses some hand-rolled CSS that's now expressible as utilities.

Plugins still in flux

By 2026, the major Tailwind plugins have v4-compatible releases:

  • `@tailwindcss/forms` — forms reset for input/select/textarea.
  • `@tailwindcss/typography`prose classes for Markdown-driven content.
  • shadcn/ui copy-paste components — updated to v4 idioms.

Smaller community plugins may still trail. If a plugin you depend on hasn't shipped v4 support, the migration tool will flag it; either contribute a fix or temporarily pin a v3.x release for that one package.

Things that surprise teams

A short list of "wait, what?" moments from Next.js upgrades:

  • OKLCH colors look different. Your brand swatch will render slightly more saturated on P3 displays. Most teams accept the change; if not, override the variable in @theme to lock in the old hex.
  • Container queries work without a plugin. If you previously installed @tailwindcss/container-queries, remove the import; the variants exist natively.
  • CSS-modules + Tailwind still work, but the nested syntax in modules now goes through Lightning CSS, not the old PostCSS nesting plugin. Strict CSS-spec-compliant nesting is fine; some quirks tolerated by the old plugin aren't.
  • Storybook picks up the new @theme automatically because it's just CSS — no extra wiring needed.

Caveat: third-party posts age fast

Malahim's piece, like any community write-up, freezes the state of the world on its publish date. Tailwind shipped v4.1 (text shadows, masks) and v4.2 (webpack plugin, logical properties, four new palettes) after most third-party posts went live. Cross-check version pins with the official upgrade guide and the GitHub releases before you merge a v4 bump to main — you may want to land on the latest 4.x rather than the announcement-day version.

Suggested upgrade order

1. Branch. Don't upgrade in main. 2. Run `npx @tailwindcss/upgrade`. Review the diff carefully. 3. `npm install` the latest tailwindcss, @tailwindcss/postcss, and any plugins. 4. Smoke test the dev server. Most failures show up immediately as missing utilities or unstyled components. 5. Visual diff the homepage, settings, and one data-heavy screen. 6. Migrate plugins, removing the ones now in core. 7. Delete `tailwind.config.ts` if you can; otherwise prune to plugin-only. 8. Merge.

The upgrade is reversible until step 8. Most Next.js teams ship it inside a single afternoon.

References

Source / further reading: Malahim's blog