Skip to content

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', {
  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', {
  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

  1. Request: Designer/developer requests a new token
  2. Review: Design system team reviews for necessity
  3. Naming: Follow naming conventions
  4. Documentation: Update token reference
  5. Communication: Announce breaking changes
  6. 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

  1. Never hardcode values - Always use tokens
  2. Use semantic names - color.text not color.gray900
  3. Layer your tokens - Primitives → Semantics → Components
  4. Document everything - Maintain token reference
  5. Test contrast - Ensure accessibility compliance
  6. Version carefully - Follow semantic versioning
  7. Support multiple themes - Light, dark, high-contrast
  8. Generate multiple formats - CSS, SCSS, JSON for different consumers
  9. Govern changes - Have a process for token modifications
  10. Provide examples - Show developers how to use tokens
Last updated Apr 3, 2026 Edit this page