All docs
Designers· 4 min

Changing a token

How to shift the brand color, retheme, or remap state colors safely.

Audience: designers (engineers can skim).

You want to change a color. Or a radius. Or the brand hue. This is how to do that without breaking anything.

The mental model

Imagine the token system as a knob with a label on it.

  • --primary is a knob labeled "primary action."
  • Today, the knob is turned to --primary-500 (which is #1464FF).
  • You can turn the knob to point at any other primary step — --primary-400 is brighter, --primary-600 is more navy.
  • Every button, every link, every focus ring updates the instant you turn the knob.

You're not painting components individually. You're changing what the knob points at.

Where the knobs live

app/globals.css. Open it and you'll see two big blocks:

:root {
  /* Layer 1 — Primitive ramps (the actual color values) */
  --primary-50:  oklch(0.970 0.013 263);
  --primary-100: oklch(0.940 0.025 263);
  ...
  --primary-950: oklch(0.220 0.085 263);

  /* (same for neutral, error, success, warning, info) */

  /* Layer 2 — Semantic tokens (the knobs) */
  --primary:        var(--primary-500);
  --primary-hover:  var(--primary-600);
  ...
}

Then a .dark { ... } block that overrides the semantic layer (not the primitive layer — the ramps themselves are mode-agnostic) for dark mode.

Three kinds of change you might want

Change 1: "Shift the brand color slightly"

You want primary to be a touch brighter / darker / cooler. Edit the primitive step — the actual oklch value — for whichever step you want to retune.

Example: brand is currently --primary-500. You want it more vivid:

/* Was */
--primary-500: oklch(0.563 0.221 263);

/* Try */
--primary-500: oklch(0.580 0.238 263);   /* a touch lighter, slightly more saturated */

Also update lib/tokens.ts with the same value so the Foundations swatch reflects reality.

Verify: reload /library/foundations, look at the Primary ramp. The 600 swatch should look how you want it. Then jump to /library/buttons and /library/data-display to confirm it reads correctly on actual components.

Change 2: "Point primary at a different step"

You want the brand to be primary-400 (brighter) or primary-600 (more authority). Don't change the ramp value. Change the semantic mapping:

/* Was */
--primary: var(--primary-500);

/* Now */
--primary: var(--primary-400);

This is a one-line change. Every component that references --primary (or its hover/active/foreground/subtle variants) updates.

Also update the state variants — if primary moves to 400, then hover should likely move to 500 (one step down from new primary), not stay at 600:

--primary:        var(--primary-400);  /* was 500 */
--primary-hover:  var(--primary-500);  /* was 600 */
--primary-active: var(--primary-600);  /* was 700 */

Always keep the resting → hover → active sequence one-step-darker each. That rhythm is part of the system feeling consistent.

Change 3: "Rebrand to a different blue (or different color entirely)"

You want the brand to be teal, or a different blue, or — at the extreme — purple. Replace the Primary primitive ramp.

  1. Generate a new 11-step ramp at the new hue. Use oklch.com or a tool you like to design the new ramp.
  2. Replace all 11 --primary-* values in globals.css.
  3. Mirror the new values into lib/tokens.ts (the PRIMARY palette object).
  4. The semantic layer (--primary → --primary-500) doesn't need to change — it still points at the right step, just a different color now.
  5. Verify on Foundations + a few component tabs.

This is a bigger change. If you're doing it, do a quick visual review of the system afterward — sometimes a new hue interacts unexpectedly with neutral choices (e.g. cool grays start looking off if you shift primary to a warm hue).

Dark mode

Dark mode is a different set of semantic mappings, not a different palette. Same ramps, different role assignments.

Find the .dark { ... } block in globals.css. The pattern is:

  • Brand colors stay brand colors (--primary still maps to --primary-500 — the brand doesn't shift with mode)
  • But --primary-hover lifts (--primary-500) instead of darkens, because on a dark background you want hover to brighten, not darken
  • Status -subtle backgrounds use a color-mix with low alpha instead of the bright 50 step
  • Status -ink text uses the 300 step (lighter) instead of the 700 step (darker)

If you change a light-mode token, always check whether the dark-mode counterpart needs adjusting too.

How to verify a token change

A change to the token layer touches everything. After the edit:

  1. Reload /library/foundations — confirm swatches look right and the semantic table shows expected mappings.
  2. Visit a few component tabs — at minimum Buttons, Inputs & Forms, and Feedback. Make sure interactive states still feel right.
  3. Toggle dark mode (top-right of sidebar) — every check above, in dark.
  4. Look at any tab where status colors appear (Feedback shows all 4 status families) — confirm the new color still reads as the intended signal.

What NOT to do

  • Don't edit a component file to "fix" a color. If a button looks wrong, the fix is at the token layer, not in button.tsx. Editing the wrapper to override a token defeats the system — the next component will have the same issue.
  • Don't add hex colors to wrappers. If you find yourself wanting to write bg-[#1464FF], that's a missing token. Add it to the semantic layer instead.
  • Don't change the semantic names. Renaming --primary to --brand is a breaking change for every consumer. Mappings can change; names can't (easily).
  • Don't mix-and-match ramps. Use the same family throughout a state ladder — if you're using --primary at rest, use --primary-hover on hover, not --info-700. Cross-ramp jumps are how systems start looking incoherent.

How to know if a token change is good

You changed the brand color. Walk through the playground — does any component now feel wrong? If yes, that's information:

  • The new color clashes with another token → consider adjusting the other token too (the system is interconnected — colors don't live in isolation).
  • The new color works on most components but breaks one → that component might be using the token in a non-standard way (worth investigating).
  • Everything looks right → ship it.

The playground exists for exactly this kind of "see it everywhere at once" verification. Use it.