I skipped design tokens in 2020. The migration took 6 months in 2023

I started a design system with hardcoded values to ship faster. Three years later the move to semantic tokens took more than six months. What I would do on day one.

The hydroburner mark in copper with green patina, on a black background

In mid-2020 I joined a B2B/B2C SaaS team as the only product designer. The UI kit in Figma had little in common with production. I started with an audit: went through the product screen by screen and collected the most used controls in one Figma file, with screenshots and notes.

The audit found up to 3 variations of the same control, hover, focus and error states written separately for each section, and hardcoded CSS colors that blocked any dark mode.

One Save changes button in three versions from three product sections: radius 8, radius 4 and a pill, with three slightly different blue fills and different hover and focus rules
The audit file: three versions of one control. A schematic of the finding

Why I started without tokens

I was the only designer and the product kept shipping. A design system that stops releases for a quarter does not get built, so I built components inside live feature work, and every new feature became a pilot for them.

Tokens would have slowed the first version. I postponed them on purpose and kept hardcoded values, knowing it was design debt. The system reached production faster, and I took the bill for later.

Where the debt showed up

The first sign came in 2021. The product blue lived in code as 3 slightly different values: #2F5BD8, #3A63E0 and #2A52C9. Nobody noticed until a customer asked for a dark mode demo. I changed the primary color in the Figma styles by hand and sent the frontend lead a list of places to fix. There were about 140 of them, and 3 releases later a few buttons were still blue the old way.

Then I estimated the migration to Variables at 2 months. It took more than 6, because every component had to be remapped from a raw value to a role by hand, and every remap needed a check in code.

A timeline of design debt: mid 2020 components with hardcoded values on purpose, 2021 to 2022 more raw values with every new component, 2023 Figma Variables and a migration of more than 6 months
How the debt grew, a schematic

The bill: 2023

In 2023 Figma introduced Variables. I rebuilt our components from scratch and mapped out 300+ variables, which became version 2.0 of the system. I ran workshops with the frontend lead to match our token structure to the updated stack.

The main change was a semantic layer. Components stopped referencing a primitive like blue-500. They reference a role, for example button-color/primary/default, and the role resolves to a base value like blue-800. Dark mode and white-labeling take a few clicks.

Three panels left to right: the Button component, the role token button-color/primary/default, and the primitive blue-800 it resolves to
Scheme, not a Figma screenshot: how a button resolves its colour
Semantic token tables in the dark theme: groups of color tokens with their values
Version 2.0: semantic color tokens in the dark theme

The migration took more than six months. We paired it with a visual refresh of the user dashboard, so I also redesigned navigation and moved the sidebars outside the main canvas, and the token work and the redesign grew into one project.

What I got for it:

  • Dark mode became possible. It is technically complete and not rolled out yet.
  • A style change goes through the whole product from Figma variables.
  • Design and engineering use the same names.
  • Code reads without a manual: button-color/primary/default says what it is.

What I would do on day one

In the retrospective of the case I wrote that I would implement tokens from the start. I still think so. AI tools could also have shortened the migration a lot.

If you have the same constraint, a solo designer and live releases, here is the minimum I would set up before the first component.

  1. Name styles by role from the first day: text-primary and surface-raised instead of gray-900 and gray-100. A role you name today is a token you do not have to invent later.
  2. Bind components to roles only. A primitive inside a component is a bug.
  3. Keep raw values in one place: one set of colors, one spacing scale, one radius list. In my system the spacing base is a 4px grid for components and 8px for layout blocks, and colors are 5 semantic groups with 10 shades each.
  4. Write down the trigger. Debt without a date does not get paid. Mine was the Variables release. Yours can be a second brand, a dark mode request or a team of three designers instead of one.
  5. Keep the migration separate from the redesign. I paired them and the project doubled in length. If the visual refresh cannot wait, plan two releases with two sets of acceptance criteria.
  6. Run a linter once the team grows. Past 200 components, hunting for detached styles by hand took hours every two weeks. I built AxoLinter, a Figma plugin that flags non-token values and shows token coverage as a percentage. How it works is in the AxoLinter project.
AxoLinter Checker before and after Fix All: token coverage grows from 41% to 93%, hardcoded fill, radius and padding become linked tokens
AxoLinter: token coverage as a percentage. Illustrative numbers for the scheme

Postponing tokens was the right call for a solo designer with live releases. The interest was 6 months of migration and a partial redesign, so today I would take that loan only with a written trigger date.

Next note

All notes
October 19, 2026 · Building, AI and agentsHow this site is built: Astro, one tokens file and an agent that follows a brief