Tailwind 4 made tokens feel closer to CSS, which is a good excuse to clean up theme decisions.
Tokens are easy to add and hard to remove. Every design system drifts toward too many names: old brand colors, new brand colors, one-off spacing, component shadows, breakpoints from a mockup, and semantic names that stopped meaning what they used to mean.
Tailwind v4 does not solve that by itself. What it does is put more of the token conversation in CSS, where the cost of a bad name is easier to see.
tokens are promises
A token is a promise about reuse.
If a value appears once, it may be a local choice. If it appears across the product, it probably deserves a name. The problem starts when the name pretends the value is more stable than it is.
@theme {
--color-blue-button: oklch(0.61 0.19 255);
--color-new-blue-button: oklch(0.58 0.21 260);
--color-marketing-blue: oklch(0.64 0.18 252);
}
That is not a theme. It is a record of drift.
primitive and semantic tokens do different work
I like separating primitive tokens from semantic tokens.
Primitive tokens describe the palette or scale:
@theme {
--color-slate-950: oklch(0.16 0.03 250);
--color-blue-600: oklch(0.55 0.18 255);
--space-3: 0.75rem;
--space-4: 1rem;
}
Semantic tokens describe product meaning:
@theme {
--color-page-bg: var(--color-slate-950);
--color-action-bg: var(--color-blue-600);
--space-card-padding: var(--space-4);
}
The split matters because a color can survive a redesign while its meaning changes, and a meaning can survive while the color changes.
component tokens need skepticism
Component tokens are tempting because they make a component easy to tune.
@theme {
--color-card-border: oklch(0.84 0.02 250);
--shadow-card: 0 1px 2px rgb(0 0 0 / 0.08);
}
That can be fine. The risk is that every component gets its own private universe. They start with subtle differences and end with a system nobody wants to touch.
Before I add a component token, I ask whether another component will use the same decision and whether changing it globally would be safe.
arbitrary values are a smell when they repeat
Arbitrary values are useful because the real world is rude and occasionally a layout needs top-[13px].
The problem is when arbitrary values become the quiet design system.
<section class="rounded-[7px] p-[18px] shadow-[0_2px_12px_rgb(0_0_0_/_0.08)]">
That line might be acceptable in a prototype. In a mature component, it tells me the system did not have the words it needed.
dark mode exposes vague names
Dark mode is a good token test.
Names like --color-gray-light and --color-gray-dark start to break down when the same component needs readable text, subtle borders, focus rings, and disabled states across themes. I would rather see names tied to role:
@theme {
--color-bg: oklch(0.99 0.01 250);
--color-fg: oklch(0.18 0.02 250);
--color-border-subtle: oklch(0.86 0.02 250);
}
@media (prefers-color-scheme: dark) {
:root {
--color-bg: oklch(0.16 0.02 250);
--color-fg: oklch(0.94 0.01 250);
--color-border-subtle: oklch(0.28 0.02 250);
}
}
The names describe use, not brightness.
the cleanup should be boring
The best token cleanup does not look heroic.
It deletes dead names. It merges duplicates. It moves one-off values back into components. It adds a few semantic names where the product actually repeats a decision. It leaves comments only where a value would otherwise look strange.
Tailwind 4 makes token work feel closer to CSS, and that closeness is useful because CSS is where those small lies become visible. The browser does not care about the story in the design tool. It computes values.
That is why I like this direction. It turns theme work into something I can read and clean up with the rest of the stylesheet.
Related posts

About Jeremy London
Engineering leader and builder in Denver. I write about AI platforms, agents, security, reliability, homelab infrastructure, and the parts of engineering work that have to survive production.