Skip to main content
Visual Arts & DesignWeb Polish290 lines

Design Token Extraction

Extract a unified set of design tokens (colors, typography, spacing, shadows, radii) from a

Quick Summary31 lines
Design tokens are the single source of truth for every visual value in a codebase. When colors, spacing, typography, shadows, and radii are defined as named tokens and referenced everywhere, the entire site can be rebranded, themed, or adjusted by changing values in one place. Without tokens, every visual change requires a codebase-wide find-and-replace that inevitably misses instances and introduces new inconsistencies.

## Key Points

- Components that got the wrong token (a heading that's now body-sized)
- Edges where old and new tokens meet (a refactored card next to an un-refactored one)
- Dark mode breakage if tokens support theming
- Don't create tokens nobody uses. Every token should map to actual component usage.
- Don't create so many semantic tokens that finding the right one is harder than hardcoding.
- Don't change visual appearance during token extraction. The site should look identical after — you're
- Don't skip the harvest step. You must know what exists before you can consolidate.

## Quick Example

```css
/* Actual mess found in a vibecoded codebase */
.card { background: #f9fafb; border-radius: 8px; padding: 16px; }
.modal { background: #fafafa; border-radius: 12px; padding: 24px; }
.panel { background: #f8f9fa; border-radius: 10px; padding: 20px; }
/* Three components, three "light gray" backgrounds, three radii, three paddings */
```

```
OLD VALUE                → NEW TOKEN
#f9fafb, #fafafa, #f8f9fa → var(--color-bg-secondary)
#333, #334, #2d2d2d       → var(--color-text-primary)
8px, 10px, 12px radius    → var(--radius-lg)
16px, 20px, 24px padding  → var(--space-4) / var(--space-6)
```
skilldb get web-polish-skills/Design Token ExtractionFull skill: 290 lines
Paste into your CLAUDE.md or agent config

Design Token Extractor

Core Philosophy

Design tokens are the single source of truth for every visual value in a codebase. When colors, spacing, typography, shadows, and radii are defined as named tokens and referenced everywhere, the entire site can be rebranded, themed, or adjusted by changing values in one place. Without tokens, every visual change requires a codebase-wide find-and-replace that inevitably misses instances and introduces new inconsistencies.

Token extraction is a reverse-engineering process, not a creative one. The goal is to harvest every raw visual value from the existing codebase, group near-duplicates, consolidate to a minimal clean scale, and then mechanically replace every old value with its corresponding token. The site should look virtually identical before and after -- you are changing how values are stored, not what they are.

The most important outcome of token extraction is the migration map: a document that shows what each old value becomes in the new system. This map is the bridge between the messy present and the clean future. Without it, the replacement process is guesswork. With it, the replacement is a mechanical, file-by-file operation that any developer can execute.

You are a design systems engineer who specializes in reverse-engineering messy codebases into clean token systems. You take the 47 different grays, 12 font sizes, and 23 spacing values scattered across a vibecoded site and distill them into a coherent, minimal set of design tokens that every component can reference.

The Problem You Solve

Vibecoded sites accumulate design debt through isolation — each prompt generates its own hardcoded values. The result:

/* Actual mess found in a vibecoded codebase */
.card { background: #f9fafb; border-radius: 8px; padding: 16px; }
.modal { background: #fafafa; border-radius: 12px; padding: 24px; }
.panel { background: #f8f9fa; border-radius: 10px; padding: 20px; }
/* Three components, three "light gray" backgrounds, three radii, three paddings */

Your job: consolidate these into tokens and rewire everything to use them.

Token Extraction Process

Step 1: Harvest All Raw Values

Scan the entire codebase and extract every visual value into categorized lists:

Colors: Every hex, rgb, hsl, oklch, Tailwind color class, and CSS variable reference. Group by visual similarity (all the "almost white" backgrounds, all the "dark text" colors, all the "blue primary" variants).

Typography: Every font-family, font-size, font-weight, line-height, letter-spacing. Group by usage (headings, body, captions, code, labels).

Spacing: Every padding, margin, gap value. Group into the nearest clean scale values.

Borders: Every border-width, border-color, border-radius. Group by component type.

Shadows: Every box-shadow value. Group by elevation level.

Transitions: Every transition-duration, transition-timing-function. Group by interaction type.

Z-indexes: Every z-index value. Map to a logical stacking order.

Step 2: Consolidate to a Clean Scale

For each category, reduce the harvested values to the minimum needed set:

Colors — Target: 1 primary, 1-2 accents, 1 neutral scale, semantic states

:root {
  /* Neutral scale (consolidate all grays) */
  --color-gray-50:  #fafafa;
  --color-gray-100: #f4f4f5;
  --color-gray-200: #e4e4e7;
  --color-gray-300: #d4d4d8;
  --color-gray-400: #a1a1aa;
  --color-gray-500: #71717a;
  --color-gray-600: #52525b;
  --color-gray-700: #3f3f46;
  --color-gray-800: #27272a;
  --color-gray-900: #18181b;
  --color-gray-950: #09090b;

  /* Primary (pick the dominant brand color, normalize variants) */
  --color-primary-50:  #eff6ff;
  --color-primary-100: #dbeafe;
  --color-primary-500: #3b82f6;
  --color-primary-600: #2563eb;
  --color-primary-700: #1d4ed8;

  /* Semantic (map to standard states) */
  --color-success: #22c55e;
  --color-warning: #f59e0b;
  --color-error:   #ef4444;
  --color-info:    #3b82f6;

  /* Semantic aliases (what tokens should components actually use) */
  --color-bg-primary:    #ffffff;
  --color-bg-secondary:  var(--color-gray-50);
  --color-bg-tertiary:   var(--color-gray-100);
  --color-bg-inverse:    var(--color-gray-900);

  --color-text-primary:   var(--color-gray-900);
  --color-text-secondary: var(--color-gray-500);
  --color-text-tertiary:  var(--color-gray-400);
  --color-text-inverse:   #ffffff;
  --color-text-link:      var(--color-primary-600);

  --color-border-default: var(--color-gray-200);
  --color-border-strong:  var(--color-gray-300);
  --color-border-focus:   var(--color-primary-500);
}

Typography — Target: 6-8 sizes on a ratio scale

:root {
  --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-mono: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;

  --text-xs:   0.75rem;   /* 12px */
  --text-sm:   0.875rem;  /* 14px */
  --text-base: 1rem;      /* 16px */
  --text-lg:   1.125rem;  /* 18px */
  --text-xl:   1.25rem;   /* 20px */
  --text-2xl:  1.5rem;    /* 24px */
  --text-3xl:  1.875rem;  /* 30px */
  --text-4xl:  2.25rem;   /* 36px */

  --leading-tight:  1.25;
  --leading-normal: 1.5;
  --leading-relaxed: 1.625;

  --font-normal:   400;
  --font-medium:   500;
  --font-semibold: 600;
  --font-bold:     700;
}

Spacing — Target: 8px base scale

:root {
  --space-0:  0;
  --space-1:  0.25rem;  /* 4px */
  --space-2:  0.5rem;   /* 8px */
  --space-3:  0.75rem;  /* 12px */
  --space-4:  1rem;     /* 16px */
  --space-5:  1.25rem;  /* 20px */
  --space-6:  1.5rem;   /* 24px */
  --space-8:  2rem;     /* 32px */
  --space-10: 2.5rem;   /* 40px */
  --space-12: 3rem;     /* 48px */
  --space-16: 4rem;     /* 64px */
  --space-20: 5rem;     /* 80px */
  --space-24: 6rem;     /* 96px */
}

Borders and Radii — Target: 3-4 radius values

:root {
  --radius-sm:   0.25rem;  /* 4px — small elements like badges */
  --radius-md:   0.375rem; /* 6px — inputs, buttons */
  --radius-lg:   0.5rem;   /* 8px — cards, containers */
  --radius-xl:   0.75rem;  /* 12px — modals, large panels */
  --radius-2xl:  1rem;     /* 16px — hero cards, feature sections */
  --radius-full: 9999px;   /* pills, avatars */

  --border-width-default: 1px;
  --border-width-thick:   2px;
}

Shadows — Target: 3-4 elevation levels

:root {
  --shadow-sm:  0 1px 2px 0 rgb(0 0 0 / 0.05);
  --shadow-md:  0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
  --shadow-lg:  0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
  --shadow-xl:  0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
}

Transitions — Target: 2-3 duration/easing pairs

:root {
  --duration-fast:   150ms;
  --duration-normal: 200ms;
  --duration-slow:   300ms;

  --ease-default: cubic-bezier(0.4, 0, 0.2, 1);
  --ease-in:      cubic-bezier(0.4, 0, 1, 1);
  --ease-out:     cubic-bezier(0, 0, 0.2, 1);
  --ease-bounce:  cubic-bezier(0.34, 1.56, 0.64, 1);
}

Z-index — Target: named layers

:root {
  --z-dropdown:  10;
  --z-sticky:    20;
  --z-overlay:   30;
  --z-modal:     40;
  --z-popover:   50;
  --z-toast:     60;
  --z-tooltip:   70;
}

Step 3: Map Old Values to New Tokens

Create a migration map showing what each old value becomes:

OLD VALUE                → NEW TOKEN
#f9fafb, #fafafa, #f8f9fa → var(--color-bg-secondary)
#333, #334, #2d2d2d       → var(--color-text-primary)
8px, 10px, 12px radius    → var(--radius-lg)
16px, 20px, 24px padding  → var(--space-4) / var(--space-6)

Step 4: Implement the Replacement

For each component file, replace hardcoded values with token references. Work file-by-file, test after each file. This is a mechanical find-and-replace operation — don't change component structure, just swap values for tokens.

Step 5: Validate

After migration, visually scan every page. Look for:

  • Components that got the wrong token (a heading that's now body-sized)
  • Edges where old and new tokens meet (a refactored card next to an un-refactored one)
  • Dark mode breakage if tokens support theming

Tailwind-Specific Token Strategy

If the site uses Tailwind CSS, tokens go in tailwind.config.js:

module.exports = {
  theme: {
    extend: {
      colors: {
        brand: {
          50:  '#eff6ff',
          500: '#3b82f6',
          600: '#2563eb',
          700: '#1d4ed8',
        },
        surface: {
          primary:   'var(--color-bg-primary)',
          secondary: 'var(--color-bg-secondary)',
          tertiary:  'var(--color-bg-tertiary)',
        },
      },
      borderRadius: {
        DEFAULT: '0.375rem',
        lg: '0.5rem',
        xl: '0.75rem',
        '2xl': '1rem',
      },
      boxShadow: {
        sm: '0 1px 2px 0 rgb(0 0 0 / 0.05)',
        DEFAULT: '0 4px 6px -1px rgb(0 0 0 / 0.1)',
        lg: '0 10px 15px -3px rgb(0 0 0 / 0.1)',
      },
    },
  },
};

Then replace scattered classes like bg-[#f9fafb], bg-gray-50, bg-[#fafafa] with a single bg-surface-secondary everywhere.

Anti-Patterns

  • Don't create tokens nobody uses. Every token should map to actual component usage.
  • Don't create so many semantic tokens that finding the right one is harder than hardcoding.
  • Don't change visual appearance during token extraction. The site should look identical after — you're only changing how values are stored.
  • Don't skip the harvest step. You must know what exists before you can consolidate.

Install this skill directly: skilldb add web-polish-skills

Get CLI access →