Design System with Tokens
Building a comprehensive design system using typestyles tokens
Design System with Tokens
A design system is more than just components—it's a complete set of standards for design and code. This guide shows how to build a token-based design system with typestyles.
Token architecture
Three-layer token system
Layer 1: Primitives (raw values)
↓
Layer 2: Semantics (meaningful tokens)
↓
Layer 3: Components (component-specific)
Layer 1: Primitives
Raw values from your brand guidelines:
ts
// tokens/primitives/colors.ts
export const primitiveColors = {
// Brand palette
brand: {
50: '#eff6ff',
100: '#dbeafe',
200: '#bfdbfe',
300: '#93c5fd',
400: '#60a5fa',
500: '#3b82f6',
600: '#2563eb',
700: '#1d4ed8',
800: '#1e40af',
900: '#1e3a8a',
},
// Neutral palette
gray: {
50: '#f9fafb',
100: '#f3f4f6',
200: '#e5e7eb',
300: '#d1d5db',
400: '#9ca3af',
500: '#6b7280',
600: '#4b5563',
700: '#374151',
800: '#1f2937',
900: '#111827',
950: '#030712',
},
// Functional colors
red: { 50: '#fef2f2', 500: '#ef4444', 900: '#7f1d1d' },
orange: { 50: '#fff7ed', 500: '#f97316', 900: '#7c2d12' },
yellow: { 50: '#fefce8', 500: '#eab308', 900: '#713f12' },
green: { 50: '#f0fdf4', 500: '#22c55e', 900: '#14532d' },
blue: { 50: '#eff6ff', 500: '#3b82f6', 900: '#1e3a8a' },
purple: { 50: '#faf5ff', 500: '#a855f7', 900: '#581c87' },
} as const;
ts
// tokens/primitives/spacing.ts
export const primitiveSpacing = {
0: '0px',
1: '4px',
2: '8px',
3: '12px',
4: '16px',
5: '20px',
6: '24px',
8: '32px',
10: '40px',
12: '48px',
16: '64px',
20: '80px',
24: '96px',
32: '128px',
40: '160px',
48: '192px',
56: '224px',
64: '256px',
} as const;
ts
// tokens/primitives/typography.ts
export const primitiveTypography = {
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
serif: ['Georgia', 'Cambria', 'serif'],
mono: ['Menlo', 'Monaco', 'monospace'],
},
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
'6xl': '3.75rem', // 60px
'7xl': '4.5rem', // 72px
},
fontWeight: {
thin: '100',
light: '300',
normal: '400',
medium: '500',
semibold: '600',
bold: '700',
extrabold: '800',
black: '900',
},
lineHeight: {
none: '1',
tight: '1.25',
snug: '1.375',
normal: '1.5',
relaxed: '1.625',
loose: '2',
},
letterSpacing: {
tighter: '-0.05em',
tight: '-0.025em',
normal: '0',
wide: '0.025em',
wider: '0.05em',
widest: '0.1em',
},
} as const;
Layer 2: Semantic tokens
Meaningful tokens that reference primitives:
ts
// tokens/semantic/colors.ts
import { tokens } from 'typestyles';
import { primitiveColors } from '../primitives/colors';
export const color = tokens.create('color', {
// Brand
brand: primitiveColors.brand[500],
brandHover: primitiveColors.brand[600],
brandActive: primitiveColors.brand[700],
brandSubtle: primitiveColors.brand[100],
brandMuted: primitiveColors.brand[200],
// Text
text: primitiveColors.gray[900],
textMuted: primitiveColors.gray[500],
textSubtle: primitiveColors.gray[400],
textInverse: '#ffffff',
textBrand: primitiveColors.brand[600],
// Backgrounds
background: primitiveColors.gray[50],
surface: '#ffffff',
surfaceRaised: primitiveColors.gray[100],
surfaceSunken: primitiveColors.gray[50],
surfaceOverlay: 'rgba(0, 0, 0, 0.5)',
// Borders
border: primitiveColors.gray[200],
borderHover: primitiveColors.gray[300],
borderActive: primitiveColors.brand[500],
// Status
success: primitiveColors.green[500],
successSubtle: primitiveColors.green[50],
warning: primitiveColors.orange[500],
warningSubtle: primitiveColors.orange[50],
danger: primitiveColors.red[500],
dangerSubtle: primitiveColors.red[50],
info: primitiveColors.blue[500],
infoSubtle: primitiveColors.blue[50],
});
ts
// tokens/semantic/spacing.ts
import { tokens } from 'typestyles';
import { primitiveSpacing } from '../primitives/spacing';
export const space = tokens.create('space', {
// Base scale
...primitiveSpacing,
// Semantic aliases
gap: primitiveSpacing[4],
gapSm: primitiveSpacing[2],
gapLg: primitiveSpacing[6],
section: primitiveSpacing[16],
sectionSm: primitiveSpacing[12],
sectionLg: primitiveSpacing[24],
component: primitiveSpacing[4],
componentSm: primitiveSpacing[3],
componentLg: primitiveSpacing[6],
});
ts
// tokens/semantic/typography.ts
import { tokens } from 'typestyles';
import { primitiveTypography } from '../primitives/typography';
export const font = tokens.create('font', {
// Family
sans: primitiveTypography.fontFamily.sans.join(', '),
serif: primitiveTypography.fontFamily.serif.join(', '),
mono: primitiveTypography.fontFamily.mono.join(', '),
// Size scale
xs: primitiveTypography.fontSize.xs,
sm: primitiveTypography.fontSize.sm,
base: primitiveTypography.fontSize.base,
lg: primitiveTypography.fontSize.lg,
xl: primitiveTypography.fontSize.xl,
'2xl': primitiveTypography.fontSize['2xl'],
'3xl': primitiveTypography.fontSize['3xl'],
'4xl': primitiveTypography.fontSize['4xl'],
// Semantic sizes
body: primitiveTypography.fontSize.base,
bodySm: primitiveTypography.fontSize.sm,
bodyLg: primitiveTypography.fontSize.lg,
headingSm: primitiveTypography.fontSize.xl,
heading: primitiveTypography.fontSize['2xl'],
headingLg: primitiveTypography.fontSize['3xl'],
headingXl: primitiveTypography.fontSize['4xl'],
display: primitiveTypography.fontSize['5xl'],
displayLg: primitiveTypography.fontSize['6xl'],
// Weight
normal: primitiveTypography.fontWeight.normal,
medium: primitiveTypography.fontWeight.medium,
semibold: primitiveTypography.fontWeight.semibold,
bold: primitiveTypography.fontWeight.bold,
// Line height
none: primitiveTypography.lineHeight.none,
tight: primitiveTypography.lineHeight.tight,
snug: primitiveTypography.lineHeight.snug,
normal: primitiveTypography.lineHeight.normal,
relaxed: primitiveTypography.lineHeight.relaxed,
loose: primitiveTypography.lineHeight.loose,
// Letter spacing
tighter: primitiveTypography.letterSpacing.tighter,
tight: primitiveTypography.letterSpacing.tight,
normal: primitiveTypography.letterSpacing.normal,
wide: primitiveTypography.letterSpacing.wide,
wider: primitiveTypography.letterSpacing.wider,
widest: primitiveTypography.letterSpacing.widest,
});
Layer 3: Component tokens
Component-specific tokens:
ts
// tokens/components/button.ts
import { tokens } from 'typestyles';
import { color, space, font } from '../semantic';
export const button = tokens.create('button', {
// Size
heightSm: '32px',
heightMd: '40px',
heightLg: '48px',
// Padding
paddingHorizontalSm: space[3],
paddingHorizontalMd: space[4],
paddingHorizontalLg: space[6],
// Border radius
borderRadius: '6px',
borderRadiusSm: '4px',
borderRadiusLg: '8px',
// Font
fontSizeSm: font.sm,
fontSizeMd: font.base,
fontSizeLg: font.lg,
fontWeight: font.medium,
// Primary variant
primaryBackground: color.brand,
primaryBackgroundHover: color.brandHover,
primaryBackgroundActive: color.brandActive,
primaryText: color.textInverse,
// Secondary variant
secondaryBackground: color.surface,
secondaryBackgroundHover: color.surfaceRaised,
secondaryBorder: color.border,
secondaryText: color.text,
// Focus ring
focusRingColor: color.brand,
focusRingWidth: '2px',
focusRingOffset: '2px',
});
ts
// tokens/components/card.ts
import { tokens } from 'typestyles';
import { color, space } from '../semantic';
export const card = tokens.create('card', {
// Spacing
padding: space[6],
paddingSm: space[4],
paddingLg: space[8],
// Visual
borderRadius: '8px',
borderWidth: '1px',
borderColor: color.border,
background: color.surface,
// Shadow
shadow: '0 1px 3px rgba(0, 0, 0, 0.1)',
shadowHover: '0 4px 6px rgba(0, 0, 0, 0.1)',
// Header
headerPadding: space[4],
headerBorderColor: color.border,
// Body
bodyPadding: space[6],
// Footer
footerPadding: space[4],
footerBackground: color.surfaceRaised,
footerBorderColor: color.border,
});
Token documentation
Token reference table
Create documentation for your tokens:
md
# Color Tokens
## Brand
| Token | Value | Usage |
| ------------------- | ------- | ---------------------- |
| `color.brand` | #3b82f6 | Primary buttons, links |
| `color.brandHover` | #2563eb | Hover states |
| `color.brandSubtle` | #dbeafe | Light backgrounds |
## Text
| Token | Value | Usage |
| ------------------- | ------- | ------------------------ |
| `color.text` | #111827 | Primary text |
| `color.textMuted` | #6b7280 | Secondary text |
| `color.textInverse` | #ffffff | Text on dark backgrounds |
## Status
| Token | Value | Usage |
| --------------- | ------- | -------------- |
| `color.success` | #22c55e | Success states |
| `color.danger` | #ef4444 | Error states |
Token usage examples
ts
// Always use tokens, never hardcode values
// ❌ Bad
const button = styles.create('button', {
base: {
padding: '8px 16px', // Hardcoded
backgroundColor: '#3b82f6', // Hardcoded
},
});
// ✅ Good
const button = styles.create('button', {
base: {
padding: `${buttonToken.paddingHorizontalMd} ${buttonToken.paddingVerticalMd}`,
backgroundColor: buttonToken.primaryBackground,
},
});
Theme variations
Dark theme
ts
// tokens/themes/dark.ts
import { tokens } from 'typestyles';
import { primitiveColors } from '../primitives/colors';
export const darkTheme = tokens.createTheme('dark', {
base: {
color: {
brand: primitiveColors.brand[400],
brandHover: primitiveColors.brand[300],
brandActive: primitiveColors.brand[500],
text: primitiveColors.gray[100],
textMuted: primitiveColors.gray[400],
textSubtle: primitiveColors.gray[500],
textInverse: primitiveColors.gray[900],
background: primitiveColors.gray[900],
surface: primitiveColors.gray[800],
surfaceRaised: primitiveColors.gray[700],
surfaceSunken: primitiveColors.gray[950],
border: primitiveColors.gray[700],
borderHover: primitiveColors.gray[600],
},
},
});
High contrast theme
ts
// tokens/themes/high-contrast.ts
import { tokens } from 'typestyles';
export const highContrastTheme = tokens.createTheme('high-contrast', {
base: {
color: {
text: '#000000',
background: '#ffffff',
border: '#000000',
brand: '#0000ff',
success: '#006600',
warning: '#cc6600',
danger: '#cc0000',
},
},
});
Token naming conventions
Consistent naming
Category + Property + Variant + State
Examples:
- color.text (category.property)
- color.textMuted (category.property.variant)
- color.textMutedHover (category.property.variant.state)
- button.primaryBackground (category.property)
- button.primaryBackgroundHover (category.property.state)
Property modifiers
Base: color.brand
Subtle: color.brandSubtle
Muted: color.brandMuted
Hover: color.brandHover
Active: color.brandActive
Disabled: color.brandDisabled
Inverse: color.brandInverse
Token validation
Type checking
ts
// Ensure all required tokens exist
interface RequiredTokens {
brand: string;
text: string;
background: string;
// ... etc
}
// TypeScript will catch missing tokens
const requiredTokens: RequiredTokens = {
brand: color.brand,
text: color.text,
background: color.background,
};
Contrast checking
ts
// WCAG contrast ratio helper
function getContrastRatio(color1: string, color2: string): number {
// Implementation
}
// Ensure accessible contrast
const contrastRatio = getContrastRatio(color.text, color.background);
if (contrastRatio < 4.5) {
console.warn('Text contrast does not meet WCAG AA standards');
}
Token distribution
NPM package structure
@myorg/design-tokens/
├── package.json
├── tokens/
│ ├── primitives/
│ ├── semantic/
│ ├── components/
│ └── themes/
└── dist/
├── tokens.css # CSS custom properties
├── tokens.json # JSON format
└── tokens.scss # SCSS variables
Multiple formats
ts
// Build script to generate multiple formats
import { tokens } from './tokens';
// CSS custom properties
const css = generateCSS(tokens);
// SCSS variables
const scss = generateSCSS(tokens);
// JSON
const json = JSON.stringify(tokens, null, 2);
// Write to files
fs.writeFileSync('dist/tokens.css', css);
fs.writeFileSync('dist/tokens.scss', scss);
fs.writeFileSync('dist/tokens.json', json);
Token governance
Change process
- Request: Designer/developer requests a new token
- Review: Design system team reviews for necessity
- Naming: Follow naming conventions
- Documentation: Update token reference
- Communication: Announce breaking changes
- Deprecation: Mark old tokens as deprecated before removal
Versioning
json
{
"name": "@myorg/design-tokens",
"version": "2.1.0",
"description": "Design tokens for the Acme design system"
}
Breaking changes (new major version):
- Removing tokens
- Renaming tokens
- Changing token values significantly
Non-breaking changes (new minor/patch):
- Adding new tokens
- Small value adjustments
- Adding new themes
Best practices summary
- Never hardcode values - Always use tokens
- Use semantic names -
color.textnotcolor.gray900 - Layer your tokens - Primitives → Semantics → Components
- Document everything - Maintain token reference
- Test contrast - Ensure accessibility compliance
- Version carefully - Follow semantic versioning
- Support multiple themes - Light, dark, high-contrast
- Generate multiple formats - CSS, SCSS, JSON for different consumers
- Govern changes - Have a process for token modifications
- Provide examples - Show developers how to use tokens