Skip to main content

Command Palette

Search for a command to run...

How to Generate a Complete CSS Design Token System from a Single Brand Color

Generate a complete CSS variable set and semantic token mapping from one brand color

Published
8 min read
E
Free Online Calculators, Generators, Developer Tools, and Data Lists. Use powerful online tools instantly in your browser. Explore calculators, generators, developer utilities, and free downloadable data lists. No signup required to start. Optional Pro features unlock advanced tools, history, and an ad-free experience.

Design systems collapse in one predictable way: the color palette gets defined once, then grows through exception. A developer needs a slightly lighter button background. A designer exports a new shade from Figma. Someone hardcodes a hex value directly into a component because there was no token for the exact shade they needed. Six months later, the codebase has forty-something color values and no one knows which ones are canonical.

The standard solution is design tokens - a layer of named, semantic values that sit between your raw palette and your component code. The problem is that building a token system from scratch is tedious. You need a tonal scale for each color, a semantic mapping for each use case, and export formats that work with your toolchain. Most teams skip the systematic approach and pay for it every time they touch the theme.

This guide walks through how to build a proper token system from a single brand color, what decisions you need to make before generating anything, and where to use the tooling to handle the mechanical work.

Designer working at a computer with color palette tools open on screen Photo by Eathan Hood on Pexels

What Design Tokens Actually Are

A design token is a name-value pair where the name encodes meaning and the value encodes a specific design decision. The token --color-surface-primary is more useful than --color-white because it describes purpose rather than appearance. When you add dark mode, you remap --color-surface-primary to a dark shade without touching any component that uses it.

The Design Tokens Community Group at W3C has been working on a standard format for tokens since 2019. Their draft specification describes tokens in three tiers: raw values, primitive tokens (named palette entries), and semantic tokens (purpose-specific mappings). Understanding this three-tier structure is useful before you start generating anything, because the decisions you make at each tier propagate outward.

CSS implements the primitive and semantic token layers through custom properties, which have been in browsers since 2016. A custom property defined in :root is available to every element in the document. Child elements can override it locally. This inheritance model maps well to how a token system works: global palette values defined at root, component-local overrides applied closer to the elements that need them.

:root {
  /* Primitive tokens - the raw palette */
  --color-blue-50: hsl(220, 90%, 95%);
  --color-blue-100: hsl(220, 90%, 88%);
  --color-blue-500: hsl(220, 90%, 50%);
  --color-blue-700: hsl(220, 90%, 30%);
  --color-blue-900: hsl(220, 90%, 12%);

  /* Semantic tokens - purpose-specific mappings */
  --color-interactive: var(--color-blue-500);
  --color-interactive-hover: var(--color-blue-700);
  --color-surface-accent: var(--color-blue-50);
  --color-text-on-accent: var(--color-blue-900);
}

This pattern, where semantic tokens reference primitive tokens by name rather than repeating their values, means a full palette update requires changing only the primitive layer.

The MDN reference on custom property inheritance covers how custom properties cascade and what happens when they are set to invalid values. The CSS Custom Properties specification is the authoritative source for browser behavior edge cases.

Step-by-Step: Generating a Token System

Step 1: Define Your Source Colors

Before generating anything, decide how many distinct color families your system needs. A typical web application requires:

  • One primary color (your brand color) - the color used for primary interactive elements, links, and key UI components
  • One neutral color (usually gray or a slightly tinted gray) - used for backgrounds, borders, text, and structural elements
  • Two or three semantic colors - green for success, red for error, amber or yellow for warning

Keep this list short. Every additional color family you add requires a full tonal scale, a semantic mapping layer, and maintenance overhead going forward. Start with primary and neutral, add semantic colors only for states you know you need.

Step 2: Generate the Tonal Scale

A tonal scale for each color family should cover the full range from near-white to near-black in evenly spaced steps. Tailwind's color system uses 11 steps (50, 100, 200 through 900), and this convention has become a useful standard because it leaves room for nuance without becoming unmanageable.

The EvvyTools design token generator automates this step. Enter a brand color in any format (HEX, RGB, or HSL), and it generates the full 10-step tonal scale with values in all three formats, plus a WCAG contrast ratio for text against white and text against black at each step. This contrast information is critical for deciding which palette steps can carry readable text.

// Example: generating a tonal scale for brand blue programmatically
// Using the HSL model - hold hue and saturation, vary lightness

const generateScale = (hue, saturation) => {
  const steps = [95, 88, 80, 70, 60, 50, 40, 30, 20, 12];
  return steps.map((lightness, index) => ({
    step: index === 0 ? 50 : index * 100,
    value: `hsl(${hue}, ${saturation}%, ${lightness}%)`,
  }));
};

const brandBlueScale = generateScale(220, 90);
// Returns steps 50 through 900 in HSL notation

Step 3: Map to Semantic Tokens

Once you have your palette, the semantic mapping layer is where you make decisions about purpose. This is not mechanical - it requires judgment about how your interface works. Some standard semantic token categories:

  • Surface: backgrounds for pages, cards, modals, and overlays
  • Text: heading, body, caption, disabled, and on-brand-color variants
  • Border: default, focused, error, and divider variants
  • Interactive: default, hover, active, disabled, and focus-ring variants
  • Feedback: success, warning, error, and info variants

Each semantic token should map to exactly one palette step for each color mode (light and dark). Avoid semantic tokens that reference raw hex values directly. The whole point is to have a single lookup table that changes when the mode changes.

/* Light mode semantic tokens */
:root {
  --color-surface-page: var(--color-neutral-50);
  --color-surface-card: var(--color-white);
  --color-text-heading: var(--color-neutral-900);
  --color-text-body: var(--color-neutral-700);
  --color-interactive-default: var(--color-blue-500);
  --color-interactive-hover: var(--color-blue-600);
  --color-border-default: var(--color-neutral-200);
}

/* Dark mode overrides */
@media (prefers-color-scheme: dark) {
  :root {
    --color-surface-page: var(--color-neutral-950);
    --color-surface-card: var(--color-neutral-900);
    --color-text-heading: var(--color-neutral-50);
    --color-text-body: var(--color-neutral-300);
    --color-interactive-default: var(--color-blue-400);
    --color-interactive-hover: var(--color-blue-300);
    --color-border-default: var(--color-neutral-700);
  }
}

Step 4: Export to Your Toolchain

Design token systems need to exist in multiple formats: CSS custom properties for runtime use, Tailwind configuration for utility-class-based projects, SCSS variables for teams still on preprocessors, and W3C Design Token JSON for design tool integration.

The generator handles this. Once your palette and semantic layer are defined, you can export in any of those formats without retyping values. For Tailwind specifically:

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        brand: {
          50: 'hsl(220, 90%, 95%)',
          100: 'hsl(220, 90%, 88%)',
          200: 'hsl(220, 90%, 80%)',
          300: 'hsl(220, 90%, 70%)',
          400: 'hsl(220, 90%, 60%)',
          500: 'hsl(220, 90%, 50%)',
          600: 'hsl(220, 90%, 40%)',
          700: 'hsl(220, 90%, 30%)',
          800: 'hsl(220, 90%, 20%)',
          900: 'hsl(220, 90%, 12%)',
        },
      },
    },
  },
};

The Tailwind documentation on customizing colors explains how the extend key works versus replacing the default palette entirely. The Style Dictionary library on GitHub is the most mature open-source tool for managing multi-format token exports if you need to go beyond what a browser-based tool provides.

Code editor showing CSS custom property definitions for a design token system Photo by Nemuel Sereti on Pexels

Common Mistakes in Token System Design

Naming tokens by appearance rather than purpose. A token called --color-light-blue breaks when the design updates to a different shade of blue or when dark mode makes that shade dark. Name tokens by what they do: --color-interactive-default.

Skipping the semantic layer entirely. Some teams define a palette and then use palette tokens directly in component styles. This works until you add dark mode or try to rebrand. Without a semantic layer, every component becomes a manual update.

Creating too many one-off tokens. If a component needs a color that does not exist in your semantic layer, the correct response is to decide whether the use case warrants a new semantic token, not to hardcode a hex value. Resist the temptation to add tokens for single components.

Not checking contrast at palette creation time. The WebAIM contrast checker and WCAG 2.1 contrast requirements define minimum ratios for accessible text. If you discover contrast failures after building components, you will need to shift your entire palette, which affects every decision downstream.

"The token naming convention is the hardest decision in the whole system and also the one teams spend the least time on. If you get the names wrong, refactoring is a find-and-replace problem across every component in your codebase." - Dennis Traina, 137Foundry

Resources and Further Reading

If you want to understand the underlying color math before generating anything, the companion guide on building a web color system using HEX, RGB, and HSL covers how the three formats relate to each other and why HSL is the right format for palette generation.

A token system is only as good as the naming decisions that shape it. The tooling can generate the values, check the contrast, and export to any format your project needs. The work that matters is deciding what each token means, keeping that list small enough to maintain, and enforcing consistency through code review rather than hoping everyone remembers the rules.