For years, the tailwind.config.js file has been the central nervous system of Tailwind projects. It was where we defined tokens, extended themes, and configured plugins. However, with the release of Tailwind CSS v4 and its underlying "Oxy" engine, that mental model is obsolete.
The shift isn't just syntactic; it is architectural. You aren't just changing where you type your hex codes—you are moving from a JavaScript-dependent build configuration to a native CSS-first architecture.
The Root Cause: Why tailwind.config.js Had to Go
In Tailwind v3, the engine was written in JavaScript. To generate your CSS, Node.js had to load your config file, parse the JavaScript, resolve dependencies, and then pass that object to the engine to construct the styling AST (Abstract Syntax Tree).
This approach introduced two significant bottlenecks:
- Context Switching: Developers had to maintain style logic in a JavaScript file while writing markup in HTML/JSX and custom styles in CSS.
- Performance Overhead: The JIT engine had to constantly bridge the gap between the Node.js runtime and the CSS output.
Tailwind v4 is rewritten in Rust. To maximize the performance gains of the new engine, the configuration needed to move closer to the metal. By deprecating the JS configuration in favor of native CSS variables and the @theme directive, the engine no longer waits for JavaScript execution to understand your design tokens. It parses your CSS entry point directly, treating CSS variables as the single source of truth.
The Fix: Migrating from JS Config to Native CSS
Below is a migration path for a standard production setup. We will convert a custom color palette, font stack, and extended breakpoints from a v3 JavaScript config to the v4 CSS architecture.
1. The Legacy State (v3)
You likely have a configuration that looks like this. This file is what we are removing.
// tailwind.config.js (Legacy)
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{html,js,tsx}"],
theme: {
extend: {
colors: {
primary: '#3b82f6',
'brand-dark': '#0f172a',
},
fontFamily: {
sans: ['Inter', 'sans-serif'],
},
screens: {
'3xl': '1600px',
}
},
},
}
2. The Modern State (v4)
In v4, you delete tailwind.config.js. Instead, you configure everything in your main CSS entry point (e.g., globals.css or styles.css).
Here is the complete, valid CSS implementation using the new @theme directive.
/* src/styles/globals.css */
/* 1. Import the core Tailwind logic */
@import "tailwindcss";
/* 2. Define your design tokens using the @theme block */
@theme {
/*
Override or extend theme values using CSS variables.
Tailwind v4 automatically maps these to utility classes.
*/
/* Colors: Using OKLCH for modern color spaces */
--color-primary: oklch(60% 0.2 250);
--color-brand-dark: oklch(20% 0.02 260);
/* Fonts: Standard font-family syntax */
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
/* Breakpoints: Define the value directly */
--breakpoint-3xl: 100rem;
/* Spacing: Add custom spacing scale */
--spacing-128: 32rem;
}
/* 3. Custom Utilities (Optional) */
/*
If you need utilities that don't map to theme variables,
use standard CSS or the @utility directive.
*/
@utility container-fluid {
max-width: 100%;
padding-inline: 2rem;
}
/* 4. Layering remains for reset/base styles */
@layer base {
body {
@apply bg-white text-brand-dark font-sans antialiased;
}
}
3. Usage in Components
Once the CSS is configured, usage in your React/Vue/HTML components remains identical. The engine generates the classes based on the variables defined in the @theme block.
// src/components/Dashboard.tsx
export default function Dashboard() {
return (
<div className="min-h-screen bg-white font-sans">
{/*
Usage of configured tokens:
- text-primary maps to --color-primary
- 3xl:block maps to --breakpoint-3xl
*/}
<header className="border-b border-gray-200 py-6">
<h1 className="text-4xl font-bold text-primary">
Analytics Dashboard
</h1>
<p className="mt-2 text-brand-dark/80">
Overview of system metrics
</p>
</header>
<main className="container-fluid mx-auto mt-8 hidden 3xl:block">
{/* Large screen specific content */}
<div className="h-96 rounded-lg bg-gray-50 p-8 border border-gray-100">
Data Visualization
</div>
</main>
</div>
);
}
Technical Explanation: How It Works
Namespace Detection
The v4 engine parses the @theme block and looks for specific variable namespaces.
--color-*tells the engine to generatetext-*,bg-*,border-*, etc.--font-*tells the engine to generatefont-*utilities.--breakpoint-*generates responsive modifiers (md:,lg:,3xl:).
Variable Hoisting
Unlike standard CSS variables which are subject to DOM cascading, variables inside @theme are hoisted by the Tailwind compiler at build time. They function as configuration, not just runtime styles. However, because they are CSS variables, you can modify them dynamically at runtime (e.g., inside a .dark class) without rebuilding your CSS, provided you set them up as custom properties outside the @theme block for dynamic switching.
Automatic Content Detection
You likely noticed the content array is missing from the CSS configuration. Tailwind v4 uses automatic content detection. It crawls your project directory (respecting .gitignore), finds files that look like templates (HTML, JSX, TSX, Vue, Svelte), and generates the CSS. You generally no longer need to explicitly tell Tailwind where your files are.
Conclusion
The migration to Tailwind v4 is less about learning new class names and more about unlearning the reliance on JavaScript for style configuration. By moving configuration into CSS variables, we align our workflow with the browser's native capabilities and allow the Rust-based Oxy engine to compile our styles with near-instantaneous speed.
Delete the config file. Embrace the CSS variables. Simplicity is the ultimate sophistication.