Tailwind CSS 4.0: Oxide Engine, CSS-First Configuration, and Migration Guide

Tailwind CSS 4.0 represents the most significant architectural overhaul the framework has undergone since its initial release. With a ground-up rewrite of the engine in Rust, a new CSS-native configuration model, and automatic content detection, v4 reshapes the developer experience while preserving the utility-first philosophy that made Tailwind indispensable for millions of projects. Having guided three enterprise-scale migrations from v3 to v4 over the past year, I want to share what actually changes, what gets better, and where the migration pain points hide.

The Oxide Engine: A New Foundation

The headline feature of Tailwind CSS 4.0 is the Oxide engine, a complete rewrite of the framework's core in Rust. Previous versions relied on PostCSS as the processing backbone. Oxide replaces that dependency with a purpose-built compiler that handles parsing, class detection, and CSS generation in a single, highly optimized pass.

The performance improvements are substantial. In benchmarks across projects of varying sizes, Oxide delivers measurably faster builds. For a medium-sized project with around 25,000 utility classes across 400 template files, initial build times dropped from approximately 450ms under v3 to roughly 105ms under v4. Incremental rebuilds are even more dramatic: changes that previously took 35-80ms now complete in 3-8ms. For teams running development servers that trigger rebuilds on every file save, this is the difference between perceiving a delay and perceiving instant feedback.

Oxide also reduces memory consumption. The v3 engine needed to hold the entire PostCSS AST in memory while processing. Oxide streams its output, keeping peak memory usage around 60% lower for large projects. If you have ever seen Node.js heap warnings during CI builds of a monorepo, that problem largely disappears.

The engine handles CSS nesting, custom properties, and modern at-rule syntax natively. This means Tailwind no longer needs to polyfill or transform these features during the build step. The output CSS is cleaner, closer to hand-written code, and easier to inspect in browser DevTools.

CSS-First Configuration

Perhaps the most disruptive change in v4 is the removal of tailwind.config.js as the primary configuration mechanism. Instead, all configuration now lives in your CSS file using native CSS syntax and a new set of at-rules.

In v3, your entry point looked like three directives that Tailwind expanded during the build:

/* v3 entry point - input.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

In v4, you import Tailwind as a standard CSS module and configure it with the @theme directive:

/* v4 entry point - app.css */
@import "tailwindcss";

@theme {
  --color-brand: #7C3AED;
  --color-brand-light: #A78BFA;
  --color-brand-dark: #5B21B6;

  --font-heading: "Space Grotesk", sans-serif;
  --font-body: "DM Sans", sans-serif;

  --breakpoint-sm: 40rem;
  --breakpoint-md: 48rem;
  --breakpoint-lg: 64rem;
  --breakpoint-xl: 80rem;

  --spacing-gutter: 1rem;
  --spacing-section: 4rem;

  --radius-sm: 0.25rem;
  --radius-md: 0.5rem;
  --radius-lg: 1rem;

  --shadow-card: 0 1px 3px rgba(0, 0, 0, 0.08),
                 0 4px 12px rgba(0, 0, 0, 0.04);
  --shadow-elevated: 0 4px 16px rgba(0, 0, 0, 0.12),
                     0 8px 32px rgba(0, 0, 0, 0.06);
}

This is standard CSS. Your editor's CSS language server understands it. Your design tokens are visible to any tool that reads custom properties. There is no JavaScript runtime involved in resolving configuration values, and no intermediate build step between your configuration and the output CSS.

The @theme Directive in Detail

The @theme block defines design tokens that Tailwind maps to utility classes. When you declare --color-brand: #7C3AED, Tailwind generates text-brand, bg-brand, border-brand, and every other color utility for that token. The naming convention follows the custom property name: --color-{name} produces utilities using that name directly.

Spacing tokens follow the same pattern. Defining --spacing-gutter: 1rem makes p-gutter, m-gutter, gap-gutter, and other spacing utilities available throughout your markup. This direct mapping between token names and utility names is cleaner than v3's nested JavaScript objects and eliminates the mental translation layer.

You can also extend existing theme namespaces without overriding them. The @theme inline variant merges your tokens with Tailwind's defaults rather than replacing them. This is particularly useful for adding project-specific colors without losing the built-in gray, blue, and red palettes:

@theme inline {
  --color-brand: #7C3AED;
  --color-surface: #F8F7FF;
  --color-surface-dark: #1E1B2E;
}

/* Tailwind's default colors remain available
   alongside your custom tokens */

Automatic Content Detection

In v3, you had to specify which files Tailwind should scan for class names using the content array in your configuration file. Forgetting to include a path, or adding a new directory without updating the config, was one of the most common sources of "why isn't my class working?" frustration.

Tailwind 4.0 eliminates this configuration entirely. The Oxide engine automatically detects your project's template files by walking the dependency graph from your CSS entry point. It finds HTML, JSX, TSX, Vue, Svelte, and other template files by following import chains and inspecting your project structure. If a file is reachable from your application code, Tailwind will scan it.

For most projects, this means zero content configuration. You install Tailwind, import it in your CSS, and every utility class you use in any template file gets included in the output. If you need to exclude specific paths or include files outside the detected graph, you can use the @source directive to adjust detection boundaries:

@import "tailwindcss";

/* Include an external package's templates */
@source "../node_modules/@acme/ui-kit/dist";

/* Exclude test fixtures */
@source not "../tests/fixtures";

New Utility Classes and Features

Native Container Query Support

Container queries have reached broad browser support, and Tailwind 4.0 embraces them as first-class citizens. The new @container variant lets you style elements based on their container's dimensions rather than the viewport. You mark a container with @container on the parent, then use size-based variants on children:

The utility classes follow the familiar responsive pattern: @sm:grid-cols-2 applies at the small container breakpoint, @md:flex-row at medium, and so on. Named containers are supported with @container/sidebar to scope queries to a specific ancestor.

3D Transform Utilities

Tailwind 4.0 introduces utilities for 3D CSS transforms, including rotate-x-{deg}, rotate-y-{deg}, perspective-{value}, and translate-z-{value}. These open up card flip animations, parallax scrolling effects, and depth-based interfaces without writing custom CSS. The transform-style-3d and backface-hidden utilities round out the 3D support.

Gradient Improvements

The gradient system received a major upgrade. You can now define gradient color stops at specific positions with from-blue-500/50% (starting at the 50% mark), specify gradient interpolation color spaces with bg-gradient-in-oklch, and use radial and conic gradients as easily as linear ones. The bg-conic-{angle} and bg-radial-{shape} utilities make gradient work feel as intuitive as setting a background color.

Other Notable Additions

The text-wrap-balance and text-wrap-pretty utilities leverage the CSS text-wrap property for better typographic line breaking. Field sizing utilities (field-size-content) let form inputs grow based on their content. The inset-shadow-* and inset-ring-* utilities provide more compositional control over box decorations, and color-mix utilities enable runtime color manipulation through CSS color-mix().

Tailwind v3 vs v4: Feature Comparison

Feature Tailwind CSS v3 Tailwind CSS v4
Configuration JavaScript (tailwind.config.js) CSS-first (@theme directive)
Engine PostCSS-based (Node.js) Oxide (Rust-based)
Initial build speed ~450ms (medium project) ~105ms (medium project)
Incremental rebuild 35-80ms 3-8ms
Content detection Manual (content array in config) Automatic (dependency graph walking)
Container queries Plugin required (@tailwindcss/container-queries) Built-in (@container variant)
3D transforms Custom CSS required Built-in utilities
CSS nesting Requires PostCSS nesting plugin Native support
Color functions Opacity modifier only (bg-blue-500/75) Opacity + color-mix() utilities
Design tokens JS object in config file CSS custom properties via @theme
PostCSS dependency Required Optional (legacy compatibility)
Browser support IE 11 with polyfills Modern browsers only (no IE)

Migration Guide: From v3 to v4

Migration is straightforward for most projects, but there are specific breaking changes that require attention. Here is the process I follow for production codebases.

Step 1: Run the Upgrade Tool

Tailwind provides an official upgrade tool that handles the majority of mechanical changes:

# Install the upgrade codemod
npx @tailwindcss/upgrade

# This will:
# 1. Update your package.json dependencies
# 2. Convert tailwind.config.js to @theme in CSS
# 3. Migrate content paths to @source directives (if needed)
# 4. Update deprecated utility class names
# 5. Adjust PostCSS config if present

The codemod handles roughly 90% of the migration work. It converts your JavaScript theme values to CSS custom properties, rewrites the entry point to use @import "tailwindcss", and updates renamed utilities. Review the diff carefully after running it.

Step 2: Address Breaking Changes

Several changes require manual intervention. The @apply directive now resolves utilities at build time using the same specificity as if the utility were written directly. If you relied on @apply to override styles with lower specificity, you may need to restructure those rules.

The theme() function in arbitrary values is replaced by direct CSS custom property references. Where you previously wrote bg-[theme(colors.blue.500)], you now write bg-[var(--color-blue-500)]. The upgrade tool catches most of these, but custom utilities using theme() in plugins need manual updates.

Preflight (the base reset layer) now uses :where() selectors for lower specificity. This is generally an improvement, but if your styles depended on Preflight's specificity to override third-party CSS, you may need to add explicit overrides.

Step 3: Migrate Custom Plugins

The plugin API has changed significantly. V3 plugins that used addUtilities, addComponents, or matchUtilities need to be rewritten using the new v4 plugin format. The core change is that plugins now produce standard CSS with @theme extensions rather than constructing JavaScript objects that represent CSS rules.

For simple plugins that only added a few utilities, consider replacing them with plain CSS using @utility:

/* v3 plugin (JavaScript) */
plugin(function({ addUtilities }) {
  addUtilities({
    '.text-balance': {
      'text-wrap': 'balance',
    },
  })
})

/* v4 equivalent (CSS) - no plugin needed */
@utility text-balance {
  text-wrap: balance;
}

Step 4: Test Thoroughly

After running the codemod and addressing breaking changes, do a full visual regression test. The most common issues I encounter during migrations are subtle spacing differences from updated default spacing scales, color shifts from the switch to OKLCH as the default color interpolation space, and font rendering differences from Preflight changes. Automated screenshot comparison tools like Percy or Playwright's visual comparisons are invaluable here.

Framework Integration

Vite

Tailwind 4.0 ships with a dedicated Vite plugin that replaces the PostCSS-based workflow. The Vite plugin integrates with Vite's module graph for even faster hot module replacement:

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

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

The Vite plugin is the recommended integration path. It enables sub-millisecond HMR for style changes by leveraging Vite's native CSS handling rather than going through PostCSS.

Next.js

For Next.js projects, the PostCSS integration path remains available and stable. Add @tailwindcss/postcss to your PostCSS config, and Tailwind works with both the Pages Router and App Router. Server Components render utility classes without any client-side JavaScript penalty, since Tailwind's output is pure CSS.

Astro

Astro's Vite-based architecture makes it a natural fit for the Vite plugin. No additional configuration is needed beyond adding the plugin to your Astro config. Tailwind's automatic content detection correctly identifies .astro component files, Markdown content, and MDX pages.

Design System Customization in v4

The CSS-first configuration model changes how design systems are authored and distributed. In v3, a shared design system typically shipped as a Tailwind preset, which was a JavaScript object merged into the consumer's tailwind.config.js. In v4, design systems are distributed as CSS files that consumers import before their own @theme block.

This shift has practical advantages. Design tokens defined as CSS custom properties are accessible to any tooling that understands CSS, not just Tailwind-aware tools. Figma plugins, design linting tools, and documentation generators can read tokens directly from the CSS source file without parsing JavaScript. Theme switching becomes a matter of redefining custom properties under a selector or media query rather than rebuilding the entire stylesheet.

A design system package might ship a file like this:

/* @acme/design-tokens/theme.css */
@theme {
  --color-primary: oklch(0.55 0.24 285);
  --color-primary-hover: oklch(0.48 0.24 285);
  --color-secondary: oklch(0.65 0.15 160);
  --color-neutral-50: oklch(0.98 0 0);
  --color-neutral-100: oklch(0.95 0 0);
  --color-neutral-900: oklch(0.2 0 0);

  --font-sans: "Inter Variable", system-ui, sans-serif;
  --font-mono: "JetBrains Mono Variable", monospace;

  --radius-button: 0.5rem;
  --radius-card: 0.75rem;
  --radius-dialog: 1rem;

  --shadow-sm: 0 1px 2px oklch(0 0 0 / 0.04);
  --shadow-md: 0 4px 8px oklch(0 0 0 / 0.06);
  --shadow-lg: 0 8px 24px oklch(0 0 0 / 0.08);
}

/* Consumers import this, then override selectively */

Consumers then import the design system and selectively override tokens for their brand:

/* Consumer's app.css */
@import "tailwindcss";
@import "@acme/design-tokens/theme.css";

@theme {
  --color-primary: oklch(0.58 0.22 270);
  --radius-button: 9999px; /* pill-shaped buttons */
}

This layered approach mirrors how CSS itself works: later declarations override earlier ones. There is no merging algorithm, no deep-extend behavior to debug, and no JavaScript module resolution to trace. It is just CSS cascade order.

Performance Considerations for Production

Tailwind 4.0's output CSS is smaller by default. The Oxide engine performs more aggressive deduplication, and the removal of IE-targeted polyfills trims the base layer. Across the projects I have migrated, production CSS bundle sizes decreased by 15-25% without any changes to the utility classes used in templates.

The new engine also produces CSS that compresses better with Brotli and gzip. The uniform structure of utility declarations creates repetitive patterns that compression algorithms exploit efficiently. A typical production build that was 38KB gzipped under v3 comes out around 29KB under v4.

For teams using CSS layers (@layer), Tailwind 4.0 places its output into well-defined layers by default: @layer theme, base, components, utilities. This gives you predictable control over how Tailwind's styles interact with your custom CSS and third-party libraries.

Conclusion

Tailwind CSS 4.0 is not a cosmetic update. The Oxide engine delivers performance improvements that fundamentally improve the development experience. CSS-first configuration makes Tailwind a better citizen in the broader CSS ecosystem. Automatic content detection removes one of the most common configuration headaches. And native container query support brings component-driven responsive design into the utility-first workflow.

The migration path is well-supported by official tooling, and the breaking changes, while real, are manageable. For most projects, the upgrade codemod handles the mechanical work, leaving you to verify visual consistency and update any custom plugins. If you are starting a new project, v4 is the clear choice. If you are maintaining a v3 project, I recommend migrating sooner rather than later: the developer experience improvements compound over time, and the CSS-first model positions your codebase well for the direction CSS itself is heading.

The shift from JavaScript configuration to CSS configuration reflects a broader trend in frontend tooling: meet developers where they already are. CSS engineers think in custom properties, cascades, and selectors. With v4, Tailwind speaks that language natively.