design-tokens

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Design Token System

设计令牌系统

Single source of truth for visual design decisions with accessibility compliance.
这是视觉设计决策的唯一可信来源,同时满足无障碍合规要求。

When to Use This Skill

何时使用该技能

  • Building a consistent design system
  • Need WCAG AA compliant color contrast
  • Want type-safe token access
  • Supporting multiple frameworks (Tailwind, CSS-in-JS)
  • 构建一致的设计系统
  • 需要符合WCAG AA标准的色彩对比度
  • 希望获得类型安全的令牌访问方式
  • 支持多种框架(Tailwind、CSS-in-JS)

Core Concepts

核心概念

Design tokens provide:
  1. Typography - Font families, sizes, weights, presets
  2. Colors - Semantic palettes with contrast ratios
  3. Type safety - TypeScript types for autocomplete
  4. Framework agnostic - Export to CSS vars, Tailwind, etc.
设计令牌提供以下能力:
  1. 排版 - 字体族、字号、字重、预设样式
  2. 色彩 - 带对比度比例的语义化调色板
  3. 类型安全 - 用于自动补全的TypeScript类型
  4. 框架无关 - 可导出为CSS变量、Tailwind配置等

Implementation

实现方案

TypeScript

TypeScript

typescript
// tokens/typography.ts
export const fontFamily = {
  sans: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
  mono: '"SF Mono", Monaco, "Cascadia Code", Consolas, monospace',
} as const;

export const fontSize = {
  xs: '12px',
  sm: '14px',
  base: '16px',
  lg: '18px',
  xl: '20px',
  '2xl': '24px',
  '3xl': '30px',
  '4xl': '36px',
} as const;

export const fontWeight = {
  normal: 400,
  medium: 500,
  semibold: 600,
  bold: 700,
} as const;

export const lineHeight = {
  tight: 1.2,
  snug: 1.3,
  normal: 1.5,
  relaxed: 1.625,
} as const;

// Typography presets for consistent usage
export const typographyPresets = {
  h1: {
    fontSize: fontSize['3xl'],
    fontWeight: fontWeight.bold,
    lineHeight: lineHeight.tight,
  },
  h2: {
    fontSize: fontSize['2xl'],
    fontWeight: fontWeight.semibold,
    lineHeight: lineHeight.snug,
  },
  h3: {
    fontSize: fontSize.xl,
    fontWeight: fontWeight.semibold,
    lineHeight: lineHeight.snug,
  },
  body: {
    fontSize: fontSize.base,
    fontWeight: fontWeight.normal,
    lineHeight: lineHeight.normal,
  },
  caption: {
    fontSize: fontSize.xs,
    fontWeight: fontWeight.normal,
    lineHeight: lineHeight.snug,
  },
} as const;
typescript
// tokens/typography.ts
export const fontFamily = {
  sans: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
  mono: '"SF Mono", Monaco, "Cascadia Code", Consolas, monospace',
} as const;

export const fontSize = {
  xs: '12px',
  sm: '14px',
  base: '16px',
  lg: '18px',
  xl: '20px',
  '2xl': '24px',
  '3xl': '30px',
  '4xl': '36px',
} as const;

export const fontWeight = {
  normal: 400,
  medium: 500,
  semibold: 600,
  bold: 700,
} as const;

export const lineHeight = {
  tight: 1.2,
  snug: 1.3,
  normal: 1.5,
  relaxed: 1.625,
} as const;

// Typography presets for consistent usage
export const typographyPresets = {
  h1: {
    fontSize: fontSize['3xl'],
    fontWeight: fontWeight.bold,
    lineHeight: lineHeight.tight,
  },
  h2: {
    fontSize: fontSize['2xl'],
    fontWeight: fontWeight.semibold,
    lineHeight: lineHeight.snug,
  },
  h3: {
    fontSize: fontSize.xl,
    fontWeight: fontWeight.semibold,
    lineHeight: lineHeight.snug,
  },
  body: {
    fontSize: fontSize.base,
    fontWeight: fontWeight.normal,
    lineHeight: lineHeight.normal,
  },
  caption: {
    fontSize: fontSize.xs,
    fontWeight: fontWeight.normal,
    lineHeight: lineHeight.snug,
  },
} as const;

Color Tokens

色彩令牌

typescript
// tokens/colors.ts
export const primary = {
  50: '#E6F4F5',
  100: '#CCE9EB',
  200: '#99D3D7',
  300: '#66BDC3',
  400: '#33A7AF',
  500: '#21808D',  // Main primary
  600: '#1A6671',
  700: '#144D55',
  800: '#0D3338',
  900: '#071A1C',
} as const;

export const neutral = {
  50: '#F8F9FA',
  100: '#F1F3F5',
  200: '#E9ECEF',
  300: '#DEE2E6',
  400: '#CED4DA',
  500: '#ADB5BD',
  600: '#868E96',
  700: '#495057',
  800: '#343A40',
  900: '#1F2121',
} as const;

export const semantic = {
  success: { light: '#86efac', main: '#218081', dark: '#16a34a' },
  warning: { light: '#fde047', main: '#A84F2F', dark: '#92400E' },
  error: { light: '#fca5a5', main: '#C0152F', dark: '#9f1239' },
  info: { light: '#99D3D7', main: '#62756E', dark: '#475569' },
} as const;

// Semantic background colors
export const background = {
  default: '#1F2121',
  surface: '#262828',
  elevated: '#334155',
  overlay: 'rgba(31, 33, 33, 0.8)',
} as const;

// Text colors with WCAG contrast ratios
export const text = {
  primary: '#FCFCF9',    // 15.8:1 - AAA
  secondary: '#B8BABA',  // 8.2:1 - AAA
  tertiary: '#9A9E9E',   // 5.8:1 - AA
  muted: '#7D8282',      // 4.5:1 - AA minimum
  link: '#32B8C6',       // 6.2:1 - AA
} as const;

export const border = {
  default: 'rgba(167, 169, 169, 0.20)',
  subtle: 'rgba(119, 124, 124, 0.30)',
  strong: '#777C7C',
  focus: '#21808D',
} as const;
typescript
// tokens/colors.ts
export const primary = {
  50: '#E6F4F5',
  100: '#CCE9EB',
  200: '#99D3D7',
  300: '#66BDC3',
  400: '#33A7AF',
  500: '#21808D',  // Main primary
  600: '#1A6671',
  700: '#144D55',
  800: '#0D3338',
  900: '#071A1C',
} as const;

export const neutral = {
  50: '#F8F9FA',
  100: '#F1F3F5',
  200: '#E9ECEF',
  300: '#DEE2E6',
  400: '#CED4DA',
  500: '#ADB5BD',
  600: '#868E96',
  700: '#495057',
  800: '#343A40',
  900: '#1F2121',
} as const;

export const semantic = {
  success: { light: '#86efac', main: '#218081', dark: '#16a34a' },
  warning: { light: '#fde047', main: '#A84F2F', dark: '#92400E' },
  error: { light: '#fca5a5', main: '#C0152F', dark: '#9f1239' },
  info: { light: '#99D3D7', main: '#62756E', dark: '#475569' },
} as const;

// Semantic background colors
export const background = {
  default: '#1F2121',
  surface: '#262828',
  elevated: '#334155',
  overlay: 'rgba(31, 33, 33, 0.8)',
} as const;

// Text colors with WCAG contrast ratios
export const text = {
  primary: '#FCFCF9',    // 15.8:1 - AAA
  secondary: '#B8BABA',  // 8.2:1 - AAA
  tertiary: '#9A9E9E',   // 5.8:1 - AA
  muted: '#7D8282',      // 4.5:1 - AA minimum
  link: '#32B8C6',       // 6.2:1 - AA
} as const;

export const border = {
  default: 'rgba(167, 169, 169, 0.20)',
  subtle: 'rgba(119, 124, 124, 0.30)',
  strong: '#777C7C',
  focus: '#21808D',
} as const;

TypeScript Types

TypeScript 类型定义

typescript
// types/tokens.ts
export type FontFamily = keyof typeof fontFamily;
export type FontSize = keyof typeof fontSize;
export type FontWeight = keyof typeof fontWeight;
export type TypographyPreset = keyof typeof typographyPresets;

export type PrimaryColor = keyof typeof primary;
export type NeutralColor = keyof typeof neutral;
export type SemanticColor = keyof typeof semantic;
export type BackgroundColor = keyof typeof background;
export type TextColor = keyof typeof text;
typescript
// types/tokens.ts
export type FontFamily = keyof typeof fontFamily;
export type FontSize = keyof typeof fontSize;
export type FontWeight = keyof typeof fontWeight;
export type TypographyPreset = keyof typeof typographyPresets;

export type PrimaryColor = keyof typeof primary;
export type NeutralColor = keyof typeof neutral;
export type SemanticColor = keyof typeof semantic;
export type BackgroundColor = keyof typeof background;
export type TextColor = keyof typeof text;

CSS Variables Export

CSS 变量导出

typescript
// tokens/export.ts
export function generateCSSVariables(): string {
  return `
:root {
  /* Typography */
  --font-sans: ${fontFamily.sans};
  --font-mono: ${fontFamily.mono};
  --text-sm: ${fontSize.sm};
  --text-base: ${fontSize.base};
  --text-lg: ${fontSize.lg};
  
  /* Colors */
  --color-primary: ${primary[500]};
  --color-primary-light: ${primary[300]};
  --color-primary-dark: ${primary[700]};
  
  /* Backgrounds */
  --bg-default: ${background.default};
  --bg-surface: ${background.surface};
  --bg-elevated: ${background.elevated};
  
  /* Text */
  --text-primary: ${text.primary};
  --text-secondary: ${text.secondary};
  --text-muted: ${text.muted};
  
  /* Borders */
  --border-default: ${border.default};
  --border-focus: ${border.focus};
}`;
}
typescript
// tokens/export.ts
export function generateCSSVariables(): string {
  return `
:root {
  /* Typography */
  --font-sans: ${fontFamily.sans};
  --font-mono: ${fontFamily.mono};
  --text-sm: ${fontSize.sm};
  --text-base: ${fontSize.base};
  --text-lg: ${fontSize.lg};
  
  /* Colors */
  --color-primary: ${primary[500]};
  --color-primary-light: ${primary[300]};
  --color-primary-dark: ${primary[700]};
  
  /* Backgrounds */
  --bg-default: ${background.default};
  --bg-surface: ${background.surface};
  --bg-elevated: ${background.elevated};
  
  /* Text */
  --text-primary: ${text.primary};
  --text-secondary: ${text.secondary};
  --text-muted: ${text.muted};
  
  /* Borders */
  --border-default: ${border.default};
  --border-focus: ${border.focus};
}`;
}

Tailwind Integration

Tailwind 集成

typescript
// tailwind.config.ts
import { primary, neutral, background, text } from './tokens/colors';

export default {
  theme: {
    extend: {
      colors: {
        primary: {
          50: primary[50],
          500: primary[500],
          900: primary[900],
          DEFAULT: primary[500],
        },
        neutral,
      },
      backgroundColor: {
        default: background.default,
        surface: background.surface,
        elevated: background.elevated,
      },
      textColor: {
        primary: text.primary,
        secondary: text.secondary,
        muted: text.muted,
      },
    },
  },
};
typescript
// tailwind.config.ts
import { primary, neutral, background, text } from './tokens/colors';

export default {
  theme: {
    extend: {
      colors: {
        primary: {
          50: primary[50],
          500: primary[500],
          900: primary[900],
          DEFAULT: primary[500],
        },
        neutral,
      },
      backgroundColor: {
        default: background.default,
        surface: background.surface,
        elevated: background.elevated,
      },
      textColor: {
        primary: text.primary,
        secondary: text.secondary,
        muted: text.muted,
      },
    },
  },
};

Usage Examples

使用示例

CSS-in-JS

CSS-in-JS

typescript
import styled from '@emotion/styled';
import { text, background, typographyPresets, border } from '@/tokens';

export const Button = styled.button`
  font-size: ${typographyPresets.body.fontSize};
  font-weight: ${typographyPresets.body.fontWeight};
  background-color: ${primary[500]};
  color: ${text.primary};
  
  &:hover {
    background-color: ${primary[400]};
  }
  
  &:focus {
    outline: 2px solid ${border.focus};
    outline-offset: 2px;
  }
`;
typescript
import styled from '@emotion/styled';
import { text, background, typographyPresets, border } from '@/tokens';

export const Button = styled.button`
  font-size: ${typographyPresets.body.fontSize};
  font-weight: ${typographyPresets.body.fontWeight};
  background-color: ${primary[500]};
  color: ${text.primary};
  
  &:hover {
    background-color: ${primary[400]};
  }
  
  &:focus {
    outline: 2px solid ${border.focus};
    outline-offset: 2px;
  }
`;

React Component

React 组件

tsx
import { typographyPresets, text } from '@/tokens';

export function Heading({ children }: { children: React.ReactNode }) {
  return (
    <h1 style={{
      ...typographyPresets.h1,
      color: text.primary,
    }}>
      {children}
    </h1>
  );
}
tsx
import { typographyPresets, text } from '@/tokens';

export function Heading({ children }: { children: React.ReactNode }) {
  return (
    <h1 style={{
      ...typographyPresets.h1,
      color: text.primary,
    }}>
      {children}
    </h1>
  );
}

WCAG Compliance

WCAG 合规性

TokenColorContrastLevel
text.primary#FCFCF915.8:1AAA
text.secondary#B8BABA8.2:1AAA
text.tertiary#9A9E9E5.8:1AA
text.muted#7D82824.5:1AA min
令牌颜色对比度等级
text.primary#FCFCF915.8:1AAA
text.secondary#B8BABA8.2:1AAA
text.tertiary#9A9E9E5.8:1AA
text.muted#7D82824.5:1AA 最低标准

Best Practices

最佳实践

  1. Use semantic tokens (text.primary not #FCFCF9)
  2. Leverage typography presets for consistency
  3. Test all text colors meet WCAG AA (4.5:1)
  4. Export TypeScript types for autocomplete
  5. Document contrast ratios for each text color
  1. 使用语义化令牌(如text.primary而非#FCFCF9)
  2. 利用排版预设保证一致性
  3. 测试所有文本色彩符合WCAG AA标准(4.5:1)
  4. 导出TypeScript类型以获得自动补全功能
  5. 记录每个文本色彩的对比度比例

Common Mistakes

常见错误

  • Using raw hex values instead of tokens
  • Not testing contrast ratios
  • Missing focus states for accessibility
  • Inconsistent typography across components
  • No dark/light mode support
  • 使用原始十六进制值而非令牌
  • 未测试对比度比例
  • 缺失无障碍所需的焦点状态
  • 组件间排版不一致
  • 不支持深色/浅色模式

Related Patterns

相关模式

  • pwa-setup - Mobile app styling
  • mobile-components - Responsive design
  • pwa-setup - 移动端应用样式配置
  • mobile-components - 响应式设计