Migrating a 200-component design system to Tailwind v4
Native CSS variables, the new @theme directive, and what we learned tearing out tailwind.config.js for good.
Tailwind v4's biggest change isn't a utility class - it's the death of `tailwind.config.js` as the source of truth for your design tokens. Tokens now live in CSS, defined with `@theme`, and that one architectural shift cascades into almost everything else about how the migration goes.
Why moving tokens into CSS actually matters
With v3, your color palette lived in a JS object that Tailwind read at build time to generate utility classes. That's an extra layer of indirection between "the value" and "the CSS," and it meant design tokens couldn't participate in things CSS does natively - like `color-mix()`, cascading overrides per component, or being read by non-Tailwind tooling without a build step. Defining tokens as real CSS custom properties means they're just... CSS. Any tool that understands CSS understands your design system now.
@theme {
--color-primary: oklch(0.55 0.2 258);
--radius-lg: 0.875rem;
--font-display: "Space Grotesk", sans-serif;
}What broke, specifically
- Any plugin that reached into the v3 config object at build time needed a rewrite - the config shape simply doesn't exist the same way anymore.
- Arbitrary value syntax changed enough in edge cases (gradients, multi-value shadows) that a pure find-and-replace migration script got us 90% of the way and the last 10% needed a human.
- Class-based dark mode needed an explicit `@custom-variant` declaration - v4 doesn't assume you want it by default, which is the right call but did require a one-line fix across every project.
The 200-component reality check
We run a single shared component library across every Appesto product. At that scale, a migration isn't really about Tailwind syntax - it's about confidence that nothing visually regressed. We leaned hard on visual regression screenshots taken before and after the migration for every component in every variant, which caught a handful of subtle shadow and gradient differences that a manual review absolutely would have missed.
Was it worth it
Yes, with a caveat: do it when you have a real visual-diffing setup, not before. The performance wins (v4's Rust-based engine is meaningfully faster on large class lists) and the architectural cleanliness of CSS-native tokens are real, but they're not worth shipping silent visual regressions to chase.
Written by Appesto Engineering.