Color Formats & Technical Guides

Design Tokens and Color Systems: From Palette to Code

How primitive, semantic and component color tokens fit together, how to build scales and themes with contrast built in, and how to share tokens with code.

By ColorOverflow Editorial TeamUpdated November 13, 20266 min readIntermediate

A design token is a named design decision, such as "the color of body text" or "the background of a primary button", stored in a form both designers and code can read. Color tokens turn a palette into a system: instead of pasting HEX codes into hundreds of places, every screen and component asks for a token by name.

This guide explains the usual three layers of color tokens, how to name them, how to build scales and map them to light and dark themes without breaking contrast, and how to share the same tokens between design tools and code.

Three layers of tokens

  • Primitive tokens hold the raw colors, one per swatch: blue-500, gray-100, red-600. They describe what a color is and say nothing about where it goes.
  • Semantic tokens describe a role: color-text-primary, color-bg-surface, color-border-subtle, color-action. Each one points at a primitive, and the theme decides which.
  • Component tokens are optional and scoped to one part of the interface: button-primary-bg, input-border-focus. They point at semantic tokens.

Components should use semantic or component tokens, never primitives. That one rule is what makes rebranding and theming cheap: change a primitive and the brand changes; point a semantic token at a different primitive and the theme changes. The components themselves never need editing.

Naming conventions

Good token names read from general to specific, so related tokens sort together and are easy to guess. A common pattern is category, then property, then role, then state.

  • Primitives: hue plus step, such as indigo-600 or gray-100. Numbered steps leave room to add one later.
  • Semantic: color-text-primary, color-text-muted, color-bg-page, color-bg-surface, color-border-default, color-action, color-action-hover, color-on-action.
  • Status roles: color-success, color-warning, color-danger and color-info, each with a matching on-color for text placed on it.
  • Avoid colors in semantic names. color-action-blue stops making sense the day the action color changes.
  • Pick one separator and case style and keep it everywhere. Tools can convert color.text.primary to --color-text-primary or colorTextPrimary as needed.

Building the scales

Each brand and neutral color becomes a scale of steps, commonly 50 for the lightest through 950 for the darkest, with the original color at 500. The Shades and Tints Generator builds exactly that from one color. Here is its scale for the indigo #6366F1.

  • #E4E4FD
  • #CACBFA
  • #B0B2F8
  • #9698F6
  • #7D7FF3
  • #6366F1
  • #5356CA
  • #4345A4
  • #33357D
  • #242557
  • #141430

indigo-50 #E4E4FD through indigo-500 #6366F1 to indigo-950 #141430.

Open in generatorPalette detailsVisualize

Shades & TintsTurn one color into a full scale of lighter and darker versions.

Measure the scale before you assign roles. Against white, indigo-600 #5356CA reaches 5.91:1 and every darker step does better. Against the near-black #111827, indigo-400 #7D7FF3 reaches 5.23:1 and every lighter step does better. The middle step, indigo-500, gets 4.46:1 with white and 3.97:1 with #111827, so it fails for normal text with both.

Mapping scales to light and dark themes

A theme is a table that points each semantic token at a primitive. In a light theme, text and actions use dark steps on light backgrounds. In a dark theme, they flip to light steps on dark backgrounds.

  • color-bg-page: white in light, gray-900 #111827 in dark.
  • color-text-primary: gray-900 #111827 in light, gray-100 #F3F4F6 in dark.
  • color-action: indigo-600 #5356CA in light, indigo-300 #9698F6 in dark.
  • color-on-action: white in light, gray-900 #111827 in dark.

Measured, the light theme gives 5.91:1 for white text on the action color and 5.37:1 for indigo-600 links on the gray-100 #F3F4F6 surface. The dark theme gives 6.85:1 for indigo-300 on the #111827 page, 5.67:1 on a raised #1F2937 surface, and 6.85:1 for #111827 text on an indigo-300 button.

  • #FFFFFF
  • #5356CA
  • #111827
  • #9698F6

White on indigo-600 in the light theme (5.91:1), and #111827 on indigo-300 in the dark theme (6.85:1).

Open in generatorPalette detailsVisualize

Apps follow the same idea with platform-specific details, such as system dark mode and status bar colors; our guide to mobile app color systems covers those.

Read the full guideColor Systems for Mobile Apps: Roles, Themes and States

Keeping contrast guarantees in the token layer

The token layer is the best place to enforce accessibility, because every screen inherits it. Instead of checking each page, you check each pair of tokens that are designed to be used together, in every theme.

  1. List the intended pairs: text-primary on bg-page, text-muted on bg-surface, on-action on action, and so on.
  2. Set a minimum for each pair: 4.5:1 for text roles, 3:1 for borders, icons and focus rings.
  3. Check every pair in every theme, including hover and disabled states that are still meant to be read.
  4. Write the results next to the tokens, so a designer changing a primitive can see which pairs it affects.
  5. Recheck whenever a primitive changes. A small brand tweak can push a passing pair below the line.

Our guide to building an accessible UI color palette goes deeper into choosing roles and states that pass, and how to handle colors such as yellow that rarely work for text.

Contrast CheckerTest a text and background pair against WCAG AA and AAA.Read the full guideHow to Build an Accessible UI Color Palette

Sharing tokens between design tools and code

Tokens are most useful when there's one source of truth. Teams usually keep it as a JSON file in a shared repository, then generate CSS variables, Tailwind config, iOS and Android resources and design-tool variables from it. The Design Tokens Community Group published the first stable version of a common JSON format, 2025.10, in October 2025.

JSON

{
  "color": {
    "indigo": {
      "600": {
        "$type": "color",
        "$value": { "colorSpace": "srgb", "components": [0.325, 0.337, 0.792], "hex": "#5356ca" }
      }
    },
    "action": { "$value": "{color.indigo.600}" }
  }
}
A primitive and a semantic token in the Design Tokens Community Group format. The braces make color.action an alias of color.indigo.600.

In CSS the same tokens become custom properties, with semantic ones pointing at primitives through var(). Our guide to CSS custom properties for colors shows how to switch themes with them. In Tailwind CSS v4, theme colors are variables in an @theme block, and a name such as --color-indigo-600 becomes classes such as bg-indigo-600.

CSS

@theme {
  --color-indigo-700: #4345A4;
  --color-indigo-600: #5356CA;
  --color-indigo-500: #6366F1;
}
Part of the indigo scale as the Shades and Tints Generator exports it for Tailwind.

For quick projects you can skip the pipeline. The Export dialog on any palette copies it as CSS variables, Tailwind, JSON, SCSS or LESS, which is enough to seed a first set of primitives.

Read the full guideCSS Custom Properties for Colors and Themes

Common mistakes

  • Letting components use primitives directly, so dark mode needs overrides everywhere.
  • Creating a semantic token for every single use, until nobody can find the right one.
  • Assuming the brand swatch can carry text without measuring it.
  • Checking contrast in one theme only.
  • Keeping separate, hand-copied color lists in the design file and in code, which drift apart within weeks.

A color system doesn't have to be big. A few scales, a short list of roles, a measured table of pairs and one shared file will carry most products a long way.

Palette VisualizerSee your palette on real designs, outfits and rooms.

Put it into practice

Unsaved changes

“” has changes that aren't saved to your wishlist. Save them before continuing?