◎Home
← All posts

Design System Migration Is Where Brand Colors Go to Die

2026-10-03 · zhoujingkang · Founder

Migrating a design system between tools, frameworks or AI agents has a reliable pattern: the components arrive pixel-perfect, and the colors are quietly wrong. Spacing survives, typography survives, the button that was brand-colored is now generic-purple. This is not a tooling bug — it is what happens when color intent lives in prose and hex lists instead of in a file the destination can consume. Here is what actually gets lost, why style guides do not survive the trip, and the plain-text token file that does.

Components migrate fine; color intent doesn't

Modern migration — moving a shadcn + Tailwind setup into an AI design tool, syncing Figma styles to code, or regenerating a system in a new editor — handles structure mechanically. Component inventories, font stacks and spacing scales transfer because they are enumerable: the destination can read the list and reproduce it.

Color is the step that fails, and it fails the same way every time. The structural values come across, but the decisions behind them do not: which grey is a surface and which one is a border, what the muted text was tuned against, why the accent darkens on hover. The destination rebuilds something that looks right on day one and drifts the first time someone says make it pop.

What actually gets lost

It is rarely the primary color — that one is written down everywhere. What gets lost is the layer of implicit decisions around it, none of which are in the hex list, and all of which are load-bearing:

  • Hover and active steps — the darker shade of the primary that only exists as 'a bit darker' in someone's memory.
  • Grey-to-surface pairings — which of the four greys in the old system was approved against which background, and at what ratio.
  • Muted variants — the desaturated text color tuned for one specific tinted surface, not for all of them.
  • Focus and state colors — visible affordances whose contrast against adjacent surfaces was checked once, in the old system, by someone who then left.

Why prose style guides fail

The common fix is documentation: write the style guide, add it to the repo, tell the agent to follow it. The problem is the format. A guide the destination (or the model) re-reads is interpreted fresh every time — prose like 'use our muted grey for secondary text' does not say which value that is, and 'keep text accessible' is an aspiration, not a measurement. Values that are not explicitly enumerated fall back to whatever the reader guesses, and the reader is a model with a training median to imitate.

The tell that a guide has failed is subtle: the pages it governs still look coherent. Drift is quiet. The first visible symptom is usually a screenshot comparison weeks later, by which point the un-enumerated values have propagated everywhere.

A plain-text token file: roles, not hexes

The durable form is almost boring: one small plain-text file, one line per role, like surface: #0F172A. Forty lines covering the full role set — background, surface, on-surface, primary, on-primary, muted, border, and the state colors for light and dark. The style guide stops re-stating values and starts referencing roles: it explains when to use muted, never which hex muted is.

Roles hold across migrations because the destination consumes names, not re-derivations. Plain text holds because every tool, framework and agent can read it — it survives a framework switch that would break a theme JSON. And because the file is the single place a value lives, changing the brand is a one-file diff instead of a hunt through prose.

The file is only half the contract if the pairs are not measured. Each text role is checked against each surface it can appear on at definition time — 4.5:1 for body text under WCAG 2.1, with the dark set measured separately. A role set with measured pairs is enforceable; a palette with a promise attached is not.

Make the migration diffable

With roles in a file, migration changes shape. Instead of rewriting documentation into the new tool's format, you copy data and inspect a diff. The structural values migrate mechanically as before; the color file moves as-is or updates line by line. A diff that touches a hex instead of a role is a review flag — that is the exact moment drift used to happen, now it is the moment it gets caught.

New colors go through the file first

One rule keeps the contract alive after migration day: never use raw color values, only the tokens. It needs a companion, because the first time someone legitimately needs a new accent, a rule with no entry point gets quietly eroded. So pair it with the exit: new colors go through the file first. Need a new accent? It enters the file, gets measured against the surfaces it will meet, and only then exists. The rules stay honest and the file stays the single source.

If you want the file scaffolded instead of hand-written, the Brand Theme Engine derives the full role set from one brand color with every pair measured, and the colorwaykit-for-agents repository ships the plain-text format with templates for Claude Code and Cursor.

Continue with a tool

References

FAQ

How do I keep colors consistent across a design system migration?

Move color intent into a plain-text token file with role names, measure every text/background pair at definition time, and make the migration a diff of that file instead of a rewrite of documentation.

Should design tokens live in Figma or in code?

In code, as the source of truth — Figma styles can be generated from the token file. Styles stored only in design tools hold hex values without role names, which is exactly the layer that drifts in a migration.

What is color drift in a design system?

The slow divergence between the colors a system was approved with and the colors it ships — caused by implicit decisions (hover steps, muted pairings) that were never written into an enforceable file and get re-derived on every migration or new page.

Try it on your own brand color

Paste one hex into the Brand Theme Engine and get a full light & dark web theme — semantic tokens, contrast-checked, export-ready.

Open the Brand Theme Engine →