How to Build a Color Design System with Tokens

How to Build a Color Design System with Tokens

April 22, 2026
13 min read

What Are Design Tokens?

Design tokens are named variables that store design decisions - colors, spacing, typography, shadows - in a single source of truth. Instead of hardcoding #4f46e5 scattered across 50 files, you define it once as --color-brand-primary and reference that name everywhere. When the brand color changes, you update one value.

What you'll build

  • ✓ A two-layer token system (primitive → semantic)
  • ✓ Naming conventions that scale across teams
  • ✓ CSS custom properties implementation
  • ✓ Tailwind config integration
  • ✓ Dark mode token overrides
  • ✓ A workflow for exporting from ColorPeek

Layer 1: Primitive Tokens

Primitive tokens are your raw color scale - every shade of every color in your palette, named by hue and numeric value. They don't carry meaning yet, they're just the full inventory. Think of them as the paint supply.

CSS Custom Properties - Primitives

:root {
  /* Indigo scale */
  --color-indigo-50:  #eef2ff;
  --color-indigo-100: #e0e7ff;
  --color-indigo-200: #c7d2fe;
  --color-indigo-300: #a5b4fc;
  --color-indigo-400: #818cf8;
  --color-indigo-500: #6366f1;
  --color-indigo-600: #4f46e5;
  --color-indigo-700: #4338ca;
  --color-indigo-800: #3730a3;
  --color-indigo-900: #312e81;

  /* Neutral scale */
  --color-neutral-0:   #ffffff;
  --color-neutral-50:  #f8fafc;
  --color-neutral-100: #f1f5f9;
  --color-neutral-200: #e2e8f0;
  --color-neutral-500: #64748b;
  --color-neutral-900: #0f172a;
  --color-neutral-950: #020617;
}

Layer 2: Semantic Tokens

Semantic tokens reference primitives and assign purpose. They answer "what is this color for?" - not "what color is it?". This layer enables dark mode: you keep the same semantic name but point it at a different primitive.

:root {
  /* Light mode - semantic layer */
  --color-bg-base:       var(--color-neutral-0);
  --color-bg-surface:    var(--color-neutral-50);
  --color-bg-elevated:   var(--color-neutral-100);

  --color-text-primary:  var(--color-neutral-900);
  --color-text-secondary:var(--color-neutral-500);
  --color-text-disabled: var(--color-neutral-200);

  --color-brand-primary: var(--color-indigo-600);
  --color-brand-hover:   var(--color-indigo-700);
  --color-brand-subtle:  var(--color-indigo-50);

  --color-border:        var(--color-neutral-200);
  --color-focus-ring:    var(--color-indigo-400);
}

.dark {
  /* Dark mode - same names, different primitives */
  --color-bg-base:       var(--color-neutral-950);
  --color-bg-surface:    var(--color-neutral-900);
  --color-bg-elevated:   #1e1e2e; /* custom dark surface */

  --color-text-primary:  var(--color-neutral-50);
  --color-text-secondary:var(--color-neutral-400);

  --color-brand-primary: var(--color-indigo-400);
  --color-brand-hover:   var(--color-indigo-300);
  --color-brand-subtle:  rgba(99,102,241,0.15);

  --color-border:        rgba(255,255,255,0.1);
}

Tailwind Integration

Wire your CSS tokens into Tailwind so you get utility class coverage. Point Tailwind's theme to your CSS variables, and you can use classes like bg-brand-primary or text-text-secondary directly in your JSX.

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        bg: {
          base:     'var(--color-bg-base)',
          surface:  'var(--color-bg-surface)',
          elevated: 'var(--color-bg-elevated)',
        },
        text: {
          primary:   'var(--color-text-primary)',
          secondary: 'var(--color-text-secondary)',
          disabled:  'var(--color-text-disabled)',
        },
        brand: {
          primary: 'var(--color-brand-primary)',
          hover:   'var(--color-brand-hover)',
          subtle:  'var(--color-brand-subtle)',
        },
        border: 'var(--color-border)',
      },
    },
  },
}

Token Naming Conventions

Use a consistent category-property-variant pattern:

--color-bg-baseBackground / base layer
--color-text-primaryText / most important
--color-brand-primaryBrand / default CTA
--color-feedback-errorFeedback / error state
--color-feedback-successFeedback / success state
--color-border-subtleBorder / low contrast

Workflow: From ColorPeek to Code

Here's a practical workflow for building your token system using ColorPeek's tools:

  1. 1Generate your brand color scale (50–900) using the Tint & Shade Generator - one base hex produces the full primitive scale.
  2. 2Add up to 16 named swatches in the Palette Exporter - assign semantic names like 'bg-base', 'brand-primary', 'text-secondary'.
  3. 3Export as CSS variables or Tailwind config with one click - paste directly into your project.
  4. 4For dark mode, duplicate the export and adjust the semantic layer to point darker primitives at the appropriate values.
  5. 5Commit the token file as a shared source of truth - both designers (via Figma tokens) and developers reference it.

Key Takeaways

  • • Two layers: primitives (raw scale) + semantics (purpose)
  • • Dark mode = same semantic names, different primitive values
  • • Name by role, not by color: --color-brand-primary not --color-indigo
  • • Export once from ColorPeek, use everywhere via CSS variables
  • • Tailwind theme extension lets you use tokens as utility classes

Share this article

Build your token system now

Generate a full color scale, name your tokens, and export as CSS variables, Tailwind config, or JSON - all free.