Tailwind v4 migration notes from twelve products
The config file is gone and your tokens now live in CSS. Here is what broke, what got better, and what we'd do differently.
Hieu Nguyen
Founder & lead engineer
Published on · 6 min read
We moved our whole catalogue to Tailwind v4. Notes from the other side.
Tokens in CSS is the real change
@theme replaces the JavaScript config. Your colours, spacing and fonts become CSS custom properties, which means they are readable at runtime — by your own components, by inline styles, by a WebGL shader that needs the brand colour. That alone removed a category of duplication where the same hex lived in both tailwind.config.js and a TypeScript constant.
What broke
Arbitrary-value plugins we had written against the v3 plugin API. Most were replaced by native v4 features and simply deleted. A few needed rewriting as plain CSS, which read better afterwards.
Anything depending on the exact order of generated utilities. If you were relying on specificity accidents, v4 will find them for you.
What got better
Build times, dramatically. Colour handling in oklch, which makes generating an accessible palette from one brand colour genuinely predictable. Container queries as a first-class feature — dashboard widgets that adapt to their container rather than the viewport are now trivial.
What we'd do differently
Define the full light palette on bare :root first, then override only what changes in dark mode. We initially split tokens across both blocks, and every missing variable became a theme bug that only appeared for users whose system preference we hadn't tested.
- tailwind css v4
- css variables
- design tokens
- migration