Tailwind CSS v4: What's New and Why It Matters
A New Engine Under the Hood
Tailwind CSS v4 introduces a redesigned build engine and CSS-first configuration. Build performance depends on the project, integrations, and cache state; measure those conditions in your own repository before assigning a speedup to an upgrade.
But speed is just the beginning. The real story of v4 is a philosophical shift: CSS-first configuration.
CSS-First Configuration
In v4, you can define design tokens directly in CSS using @theme. JavaScript configuration is still supported for compatibility when explicitly loaded with @config:
@import 'tailwindcss';
@theme {
--color-bg-primary: #0d1117;
--color-bg-secondary: #161b22;
--color-accent-teal: #00adb5;
--color-accent-rose: #e2725b;
--font-heading: 'Poppins', system-ui, sans-serif;
--font-body: 'Inter', system-ui, sans-serif;
--shadow-card: 0 4px 24px rgba(0, 0, 0, 0.3);
}
This approach has several advantages:
- No context switching — Your design tokens are CSS custom properties, readable by any tool
- One place for tokens — Configuration and styles can be reviewed together
- Better IDE support — CSS files get syntax highlighting, autocomplete, and validation
- Composability — Tokens can reference other tokens using standard CSS
var()syntax
If you're migrating from v3, the
@tailwindcss/upgradeCLI handles most of the conversion automatically. It reads your JavaScript config and generates the equivalent@themeblock.
How We Structure Design Tokens
At Rosecraft Studios, we organize our @theme block into semantic categories:
@theme {
/* === Colors === */
--color-bg-primary: #0d1117;
--color-bg-secondary: #161b22;
--color-bg-card: #131920;
--color-accent-teal: #00adb5;
--color-accent-rose: #e2725b;
--color-text-primary: #e6edf3;
--color-text-muted: #8b949e;
/* === Typography === */
--font-heading: 'Poppins', system-ui, sans-serif;
--font-body: 'Inter', system-ui, sans-serif;
/* === Shadows === */
--shadow-card: 0 4px 24px rgba(0, 0, 0, 0.3);
--shadow-teal-glow: 0 4px 16px rgba(0, 173, 181, 0.25);
}
Then in components, we use only the token names — never raw hex values:
<div className="bg-bg-card border border-border rounded-lg p-6">
<h3 className="font-heading text-text-primary text-xl font-semibold">Card Title</h3>
<p className="font-body text-text-muted">Card description</p>
</div>
This discipline pays off when a client requests a theme adjustment. Changing --color-accent-teal from #00adb5 to #0891b2 updates every component that uses it — instantly, with zero search-and-replace.
Automatic Content Detection
Tailwind v4 eliminates the content array configuration. The new engine automatically detects which files use utility classes:
/* v3: Required explicit content paths */
/* content: ['./src/**/*.{js,ts,jsx,tsx}'] */
/* v4: Just import and it works */
@import 'tailwindcss';
Automatic detection has exclusions, including ignored files and dependencies. Use @source to register additional directories, such as shared components in a monorepo:
@import 'tailwindcss';
@source "../shared/components";
@source "./src";
Container Queries Are Built In
Container queries — long a CSS wish-list feature — are first-class in v4:
<div className="@container">
<div className="@sm:flex @sm:gap-4 @md:grid @md:grid-cols-3">
{/* Layout responds to container width, not viewport */}
</div>
</div>
Container queries are especially powerful for component libraries. A card component can adapt its layout based on the space available to it, regardless of where it's placed in the page.
Measuring an Upgrade
Record clean-build time, rebuild time after a representative edit, generated CSS size, and peak memory before and after the migration. Use the same machine and dependencies, repeat the measurements, and keep the output alongside the pull request. A faster compiler does not automatically mean a faster page for visitors.
Breaking Changes to Know About
The migration isn't entirely seamless. Key breaking changes:
Utility and Configuration Changes
Review the official upgrade guide for renamed shadow and radius utilities, ring defaults, and browser requirements. Use a representative set of pages for visual checks, including focus, hover, and responsive states.
Legacy Plugins and CSS Utilities
@plugin loads a legacy JavaScript plugin. It does not replace the JavaScript plugin() helper with a CSS file loader:
@import 'tailwindcss';
@plugin '@tailwindcss/typography';
For a utility defined in CSS, use @utility:
@utility content-auto {
content-visibility: auto;
}
See the directive reference.
Dark Mode
The dark variant uses prefers-color-scheme by default. To control it through a .dark class, override the variant:
@import 'tailwindcss';
@custom-variant dark (&:where(.dark, .dark *));
See Tailwind's dark-mode documentation.
Planning a Migration
Check browser support and plugin compatibility first. Run the upgrade tool on a branch, review the generated changes, then compare key layouts and interaction states. Treat a migration as a code and visual review, with enough room to investigate regressions.
Need help with a Tailwind v4 migration or building a design system? Reach out — we've done this for multiple production applications and know where the gotchas are.
