Front-end Engineering Lab
PatternsDesign Systems

Design Tokens

Token architecture for scalable, maintainable design systems

Design Tokens

Design tokens are the single source of truth for design decisions. They're named entities that store visual design attributes, making it easy to maintain a consistent visual system.

What Are Design Tokens?

Instead of hardcoding values like #0066CC or 16px, you use tokens:

// ❌ Without tokens
const button = {
  backgroundColor: '#0066CC',
  padding: '12px 24px',
  fontSize: '14px',
  borderRadius: '6px',
};

// ✅ With tokens
const button = {
  backgroundColor: tokens.color.primary,
  padding: `${tokens.spacing.md} ${tokens.spacing.lg}`,
  fontSize: tokens.fontSize.sm,
  borderRadius: tokens.borderRadius.md,
};

Benefits:

  • Single source of truth: Change once, update everywhere
  • Consistency: Same values across all components
  • Theming: Swap token sets for different themes
  • Platform-agnostic: Use in web, iOS, Android
  • Designer-developer sync: Shared language

Token Types

1. Color Tokens

// tokens/colors.ts
export const colors = {
  // Primitive colors (palette)
  blue: {
    50: '#EFF6FF',
    100: '#DBEAFE',
    200: '#BFDBFE',
    300: '#93C5FD',
    400: '#60A5FA',
    500: '#3B82F6',  // Base
    600: '#2563EB',
    700: '#1D4ED8',
    800: '#1E40AF',
    900: '#1E3A8A',
  },
  
  gray: {
    50: '#F9FAFB',
    100: '#F3F4F6',
    200: '#E5E7EB',
    300: '#D1D5DB',
    400: '#9CA3AF',
    500: '#6B7280',
    600: '#4B5563',
    700: '#374151',
    800: '#1F2937',
    900: '#111827',
  },
  
  // Semantic colors (usage)
  semantic: {
    // Brand
    primary: '#3B82F6',      // blue-500
    secondary: '#6B7280',    // gray-500
    
    // Backgrounds
    'bg-default': '#FFFFFF',
    'bg-subtle': '#F9FAFB',  // gray-50
    'bg-muted': '#F3F4F6',   // gray-100
    
    // Text
    'text-default': '#111827', // gray-900
    'text-muted': '#6B7280',   // gray-500
    'text-subtle': '#9CA3AF',  // gray-400
    
    // Borders
    'border-default': '#E5E7EB', // gray-200
    'border-hover': '#D1D5DB',   // gray-300
    'border-focus': '#3B82F6',   // blue-500
    
    // Feedback
    success: '#10B981',
    warning: '#F59E0B',
    error: '#EF4444',
    info: '#3B82F6',
  },
};

2. Spacing Tokens

// tokens/spacing.ts
export const spacing = {
  0: '0',
  1: '0.25rem',  // 4px
  2: '0.5rem',   // 8px
  3: '0.75rem',  // 12px
  4: '1rem',     // 16px
  5: '1.25rem',  // 20px
  6: '1.5rem',   // 24px
  8: '2rem',     // 32px
  10: '2.5rem',  // 40px
  12: '3rem',    // 48px
  16: '4rem',    // 64px
  20: '5rem',    // 80px
  24: '6rem',    // 96px
  
  // Semantic spacing
  xs: '0.25rem',  // spacing[1]
  sm: '0.5rem',   // spacing[2]
  md: '1rem',     // spacing[4]
  lg: '1.5rem',   // spacing[6]
  xl: '2rem',     // spacing[8]
  '2xl': '2.5rem', // spacing[10]
  '3xl': '3rem',  // spacing[12]
};

3. Typography Tokens

// tokens/typography.ts
export const typography = {
  // Font families
  fontFamily: {
    sans: [
      'Inter',
      '-apple-system',
      'BlinkMacSystemFont',
      'Segoe UI',
      'sans-serif',
    ].join(', '),
    mono: ['JetBrains Mono', 'Menlo', 'Monaco', 'monospace'].join(', '),
  },
  
  // Font sizes
  fontSize: {
    xs: '0.75rem',    // 12px
    sm: '0.875rem',   // 14px
    base: '1rem',     // 16px
    lg: '1.125rem',   // 18px
    xl: '1.25rem',    // 20px
    '2xl': '1.5rem',  // 24px
    '3xl': '1.875rem', // 30px
    '4xl': '2.25rem', // 36px
    '5xl': '3rem',    // 48px
  },
  
  // Font weights
  fontWeight: {
    normal: '400',
    medium: '500',
    semibold: '600',
    bold: '700',
  },
  
  // Line heights
  lineHeight: {
    none: '1',
    tight: '1.25',
    snug: '1.375',
    normal: '1.5',
    relaxed: '1.625',
    loose: '2',
  },
  
  // Letter spacing
  letterSpacing: {
    tighter: '-0.05em',
    tight: '-0.025em',
    normal: '0',
    wide: '0.025em',
    wider: '0.05em',
    widest: '0.1em',
  },
};

4. Border Radius Tokens

// tokens/borderRadius.ts
export const borderRadius = {
  none: '0',
  sm: '0.125rem',  // 2px
  base: '0.25rem', // 4px
  md: '0.375rem',  // 6px
  lg: '0.5rem',    // 8px
  xl: '0.75rem',   // 12px
  '2xl': '1rem',   // 16px
  '3xl': '1.5rem', // 24px
  full: '9999px',
};

5. Shadow Tokens

// tokens/shadows.ts
export const shadows = {
  none: 'none',
  sm: '0 1px 2px 0 rgb(0 0 0 / 0.05)',
  base: '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)',
  md: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
  lg: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)',
  xl: '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)',
  '2xl': '0 25px 50px -12px rgb(0 0 0 / 0.25)',
  inner: 'inset 0 2px 4px 0 rgb(0 0 0 / 0.05)',
};

6. Breakpoint Tokens

// tokens/breakpoints.ts
export const breakpoints = {
  xs: '320px',
  sm: '640px',
  md: '768px',
  lg: '1024px',
  xl: '1280px',
  '2xl': '1536px',
};

Token Organization

Flat Structure (Simple)

// tokens.ts
export const tokens = {
  colorPrimary: '#3B82F6',
  colorSecondary: '#6B7280',
  spacing1: '0.25rem',
  spacing2: '0.5rem',
  fontSizeSm: '0.875rem',
  // ...
};

Pros: Simple, easy to use
Cons: Hard to organize, name collisions

// tokens/index.ts
export const tokens = {
  color: {
    primary: '#3B82F6',
    secondary: '#6B7280',
    // ...
  },
  spacing: {
    1: '0.25rem',
    2: '0.5rem',
    // ...
  },
  fontSize: {
    sm: '0.875rem',
    // ...
  },
};

Pros: Organized, scalable
Cons: Deeper nesting

Modular Structure (Best for Large Systems)

tokens/
├── index.ts          # Main export
├── colors.ts         # Color tokens
├── spacing.ts        # Spacing tokens
├── typography.ts     # Typography tokens
├── borders.ts        # Border tokens
├── shadows.ts        # Shadow tokens
└── breakpoints.ts    # Breakpoint tokens

Token Transforms (Style Dictionary)

// style-dictionary.config.js
module.exports = {
  source: ['tokens/**/*.json'],
  platforms: {
    // CSS Custom Properties
    css: {
      transformGroup: 'css',
      buildPath: 'build/css/',
      files: [{
        destination: 'tokens.css',
        format: 'css/variables'
      }]
    },
    
    // JavaScript/TypeScript
    js: {
      transformGroup: 'js',
      buildPath: 'build/js/',
      files: [{
        destination: 'tokens.js',
        format: 'javascript/es6'
      }]
    },
    
    // iOS
    ios: {
      transformGroup: 'ios',
      buildPath: 'build/ios/',
      files: [{
        destination: 'Tokens.swift',
        format: 'ios-swift/class.swift'
      }]
    },
    
    // Android
    android: {
      transformGroup: 'android',
      buildPath: 'build/android/',
      files: [{
        destination: 'tokens.xml',
        format: 'android/resources'
      }]
    }
  }
};

Using Tokens in Components

With CSS Variables

/* Generated tokens.css */
:root {
  --color-primary: #3B82F6;
  --spacing-md: 1rem;
  --font-size-sm: 0.875rem;
}

/* Use in components */
.button {
  background-color: var(--color-primary);
  padding: var(--spacing-md);
  font-size: var(--font-size-sm);
}

With Tailwind CSS

// tailwind.config.js
import { tokens } from './tokens';

export default {
  theme: {
    colors: tokens.color,
    spacing: tokens.spacing,
    fontSize: tokens.fontSize,
    fontWeight: tokens.fontWeight,
    borderRadius: tokens.borderRadius,
    boxShadow: tokens.shadows,
  },
};

With styled-components

import { tokens } from '@/tokens';
import styled from 'styled-components';

const Button = styled.button`
  background-color: ${tokens.color.primary};
  padding: ${tokens.spacing.md} ${tokens.spacing.lg};
  font-size: ${tokens.fontSize.sm};
  border-radius: ${tokens.borderRadius.md};
`;

With Vanilla Extract

// button.css.ts
import { style } from '@vanilla-extract/css';
import { tokens } from './tokens';

export const button = style({
  backgroundColor: tokens.color.primary,
  padding: `${tokens.spacing.md} ${tokens.spacing.lg}`,
  fontSize: tokens.fontSize.sm,
  borderRadius: tokens.borderRadius.md,
});

Semantic vs Primitive Tokens

Primitive (What it is)

const primitives = {
  blue500: '#3B82F6',
  spacing4: '1rem',
};

Semantic (What it means)

const semantic = {
  colorPrimary: primitives.blue500,
  spacingMd: primitives.spacing4,
};

Best Practice: Use semantic tokens in components, not primitives.

Token Naming Conventions

// Good naming
tokens.color.text.default
tokens.color.text.muted
tokens.color.bg.default
tokens.color.bg.subtle
tokens.spacing.component.padding
tokens.spacing.layout.gutter

// Avoid
tokens.darkGray
tokens.space16
tokens.bigPadding

Type Safety

// Generate types from tokens
export type ColorToken = keyof typeof tokens.color;
export type SpacingToken = keyof typeof tokens.spacing;
export type FontSizeToken = keyof typeof tokens.fontSize;

// Use in components
interface ButtonProps {
  color?: ColorToken;
  padding?: SpacingToken;
  fontSize?: FontSizeToken;
}

Best Practices

  1. Start with Primitives: Define raw values first
  2. Add Semantics: Layer meaning on top
  3. Version Control: Treat tokens as code
  4. Document Usage: Explain when to use each token
  5. Sync with Design: Keep Figma and code in sync
  6. Test Changes: Visual regression tests
  7. Gradual Migration: Don't refactor everything at once

Common Pitfalls

Too many tokens: 100 shades of gray
Curated palette (5-10 per color)

Vague names: color1, space-big
Semantic names: colorPrimary, spacingMd

No documentation: Developers guess
Clear guidelines on usage

Hardcoded values: Tokens exist but unused
Enforce token usage with linting

Design tokens are the DNA of your design system—get them right and everything else falls into place.

On this page