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
Nested Structure (Recommended)
// 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 tokensToken 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.bigPaddingType 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
- Start with Primitives: Define raw values first
- Add Semantics: Layer meaning on top
- Version Control: Treat tokens as code
- Document Usage: Explain when to use each token
- Sync with Design: Keep Figma and code in sync
- Test Changes: Visual regression tests
- 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.