Skip to main content

TailwindCSS v4 Migration Guide: What Changed and How to Upgrade

A practical guide to migrating from TailwindCSS v3 to v4. Covers the new CSS-first configuration, updated color system, removed utilities, and step-by-step upgrade path.

12 min read
TailwindCSS v4 migration showing the shift from JavaScript config to CSS-first @theme configuration

I migrated this portfolio from TailwindCSS v3 to v4, and the upgrade was smoother than expected — but there are breaking changes you need to know about. Here’s what I learned.

TAILWIND V4 IN ONE GLANCE

The upgrade is not just a version bump. It changes where configuration lives, how the tool integrates with Vite, and which utility assumptions still hold.

CONFIG

The center of gravity moves into CSS

Tokens now live in `@theme`, which makes them visible as CSS variables and easier to inspect or reuse.

  • No more JS-only theme config
  • Native CSS variables as design tokens
  • Better DevTools visibility

TOOLING

Tailwind plugs into Vite directly

The v4 setup is simpler, but it means your migration is partly a build-pipeline migration too.

  • Swap PostCSS-centric setup for the Vite plugin
  • Review config files you can delete
  • Confirm your entry CSS is updated

BREAKING EDGES

Several utility assumptions changed

Opacity syntax, shadow naming, border defaults, and CSS helper behavior can break quietly if you do not review them explicitly.

  • Check renamed utilities
  • Audit implicit border colors
  • Replace old `theme()` usage

PAYOFF

The upgrade earns its keep on performance

Faster builds, cleaner config, and easier container-query usage make v4 worth it once you absorb the mental model shift.

  • Faster full and incremental builds
  • Smaller CSS output
  • Container queries become straightforward

The Big Shift: CSS-First Configuration

The biggest change in Tailwind v4 is that configuration moves from tailwind.config.js into your CSS file using @theme.

Before (v3)

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        brand: {
          accent: '#C09E5A',
          black: '#000000',
        },
      },
      fontFamily: {
        sans: ['Inter', 'system-ui', 'sans-serif'],
        mono: ['JetBrains Mono', 'monospace'],
      },
    },
  },
  plugins: [require('@tailwindcss/typography')],
};

After (v4)

/* app.css */
@import 'tailwindcss';
@plugin '@tailwindcss/typography';

@theme {
  --font-sans: 'Inter', system-ui, sans-serif;
  --font-mono: 'JetBrains Mono', monospace;
  --color-brand-accent: #C09E5A;
  --color-brand-black: #000000;
}

This is a significant philosophical change. Your design tokens are now CSS custom properties, which means:

  • They’re inspectable in browser DevTools
  • They work with native CSS features like color-mix()
  • No build step needed to read your config values

Step-by-Step Migration

MIGRATION FLOW

Do the upgrade in clear passes. Most quiet breakage happens when teams jump from dependency bump straight to visual QA.

  1. SETUP

    Upgrade the package and move to the Vite plugin

    Tailwind v4 changes how the tool integrates, so the dependency update is also a build-pipeline update.

    • Remove the old PostCSS-only setup
    • Install `tailwindcss` and `@tailwindcss/vite`
    • Confirm Vite is the source of truth

    Why this step existsYou start from the supported runtime model instead of layering v4 on top of a v3 pipeline.

  2. CLEANUP

    Delete config that no longer drives the build

    Old config files can create false confidence if they remain in the repo after the upgrade.

    • Remove stale Tailwind config files
    • Drop PostCSS config if Tailwind was the only reason it existed
    • Simplify the mental model before class-level fixes

    Why this step existsYou reduce the chance that teammates think legacy config is still active.

  3. TOKENS

    Move theme values into CSS

    The main mental shift in v4 is that tokens live in `@theme` and become native CSS variables.

    • Port fonts, colors, breakpoints, and tokens
    • Check that token names still map to your utilities
    • Use DevTools to inspect them directly

    Why this step existsYou align the design-token layer with how v4 wants to be configured.

  4. AUDIT

    Review renamed utilities and changed defaults

    The riskiest regressions come from small assumptions: borders, shadows, blur, opacity, and old helper functions.

    • Add explicit border colors
    • Update slash-opacity syntax
    • Replace old `theme()` usage

    Why this step existsYou catch the class-level changes that make layouts look subtly wrong.

  5. VERIFY

    Run the codemod, then verify by hand

    The upgrader is useful, but it is not a replacement for reading the diff and smoke-testing real pages.

    • Run the official upgrader
    • Review the diff manually
    • Test high-traffic pages after the migration

    Why this step existsYou finish with a cleaner migration instead of a partially automated one.

1. Update Dependencies

pnpm remove tailwindcss postcss autoprefixer
pnpm add tailwindcss@latest @tailwindcss/vite

In v4, Tailwind runs as a Vite plugin instead of PostCSS. Update your vite.config.ts:

import tailwindcss from '@tailwindcss/vite';

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

2. Remove Old Config Files

Delete these if they exist:

  • tailwind.config.js / tailwind.config.ts
  • postcss.config.js (if only used for Tailwind)

3. Update Your CSS Entry Point

Replace the old directives:

/* Before */
@tailwind base;
@tailwind components;
@tailwind utilities;

/* After */
@import 'tailwindcss';

4. Migrate Theme Config to @theme

Move your tailwind.config.js theme values into @theme blocks in your CSS:

@theme {
  --font-display: 'Inter', system-ui, sans-serif;
  --color-brand-accent: #C09E5A;
  --color-brand-border: #2B2B2B;
  --breakpoint-sm: 640px;
  --breakpoint-md: 768px;
}

5. Update Plugin Usage

/* Before: require() in config */
/* After: @plugin directive in CSS */
@plugin '@tailwindcss/typography';

Breaking Changes to Watch For

Renamed Utilities

Several utility classes were renamed for consistency:

v3v4
bg-opacity-50bg-black/50 (opacity modifier)
text-opacity-75text-white/75
shadow-smshadow-xs
shadowshadow-sm
ringring-3
blurblur-sm

Removed Features

  • @apply with !important: Use @utility instead for custom utilities
  • theme() function in CSS: Replaced by native CSS custom properties (var(--color-brand-accent))
  • safelist config: Not needed — v4’s detection is more thorough
  • darkMode config: Always uses @media (prefers-color-scheme: dark) or class strategy via CSS

Default Border Color Changed

In v3, border defaulted to gray-200. In v4, it defaults to currentColor. Add explicit colors:

<!-- Before (v3) -->
<div class="border">...</div>

<!-- After (v4) — add explicit color -->
<div class="border border-gray-200">...</div>

SAFE MIGRATION PATH VS QUIET FAILURE POINTS

Most teams can upgrade without drama if they do it in deliberate passes. The risky part is assuming the codemod or dependency bump caught everything important.

SAFE PATH

Migrate in controlled passes

  • Upgrade dependencies and wire the Vite plugin first
  • Port design tokens into `@theme` before chasing class-level diffs
  • Run the official upgrader, then review the diff manually
  • Smoke-test key pages after updating border, shadow, and blur utilities

QUIET BREAKAGE

These are the places teams usually miss

  • Implicit border colors now inheriting from `currentColor`
  • Old opacity utilities that need the slash syntax
  • `theme()` usage left behind in CSS files
  • Assuming JS config and old plugins are still being read

Container Queries

Tailwind v4 has first-class container query support:

<div class="@container">
  <div class="@sm:flex @md:grid @md:grid-cols-2">
    <!-- Responds to container size, not viewport -->
  </div>
</div>

New Color System

The default color palette uses OKLCH color space, which provides more perceptually uniform colors. If you’re using custom colors, they’ll still work fine.

Automated Migration

Tailwind provides a codemod to automate most of the migration:

npx @tailwindcss/upgrade

This handles renaming utilities, updating imports, and converting your config. I’d still recommend reviewing the diff manually — the codemod caught about 90% of changes in my case.

Performance Improvements

v4 is significantly faster:

  • Build times: Up to 10x faster full builds
  • Incremental builds: Up to 100x faster during development
  • Bundle size: Smaller CSS output thanks to better dead-code elimination

In this portfolio, the CSS bundle dropped from 28KB to 19KB after migration — a 32% reduction with zero visual changes.

Key Takeaways

  • The CSS-first approach is the biggest mental shift — embrace it
  • Use the automated migration tool, but review the output
  • Update border utilities to include explicit colors
  • Shadow and blur class names have shifted — check your components
  • The performance improvements alone make the upgrade worthwhile
  • Container queries are now trivial to use
Share this article:
X LinkedIn

Written by Umesh Malik

AI Engineer & Software Developer. Building GenAI applications, LLM-powered products, and scalable systems.