atomic-design-quarks

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Atomic Design: Quarks

原子设计:Quarks

Master the creation and organization of quarks - the sub-atomic design tokens and primitive values that form the foundation of your design system. Quarks are the smallest building blocks that atoms consume.
掌握Quarks的创建与组织方法——这些亚原子级别的设计令牌和原始值是你的设计系统的基础。Quarks是原子组件所依赖的最小构建块。

What Are Quarks?

什么是Quarks?

Quarks extend Brad Frost's Atomic Design methodology by adding a level below atoms. While atoms are the smallest UI components, quarks are the smallest design values - the raw materials from which atoms are built.
Quarks are:
  • Primitive Values: Colors, spacing units, font sizes, shadows
  • Non-Visual: They don't render anything on their own
  • Design Tokens: Named constants that define your design language
  • Consumed by Atoms: Atoms import and use quarks for styling
Quarks扩展了Brad Frost的原子设计方法论,在原子层之下新增了一个层级。原子是最小的UI组件,而Quarks是最小的设计值——是构建原子组件的原始素材。
Quarks具有以下特性:
  • 原始值:颜色、间距单位、字体大小、阴影
  • 非可视化:自身不会渲染任何内容
  • 设计令牌:定义设计语言的命名常量
  • 供原子组件使用:原子组件会导入并使用Quarks来实现样式

Quarks vs Atoms

Quarks vs 原子组件

AspectQuarksAtoms
NatureValues/TokensUI Components
RenderNothing (CSS variables, constants)Visual elements
Examples
--color-primary-500
,
spacing.md
Button, Input, Label
ImportsNone (base level)Quarks only
PurposeDefine design languageImplement design language
维度Quarks原子组件
本质值/令牌UI组件
渲染结果无(仅CSS变量、常量)可视化元素
示例
--color-primary-500
,
spacing.md
按钮、输入框、标签
依赖无(基础层级)仅依赖Quarks
用途定义设计语言实现设计语言

Common Quark Types

常见Quarks类型

Color Tokens

颜色令牌

typescript
// quarks/colors.ts
export const colors = {
  // Brand colors
  primary: {
    50: '#e3f2fd',
    100: '#bbdefb',
    200: '#90caf9',
    300: '#64b5f6',
    400: '#42a5f5',
    500: '#2196f3', // Primary
    600: '#1e88e5',
    700: '#1976d2',
    800: '#1565c0',
    900: '#0d47a1',
  },

  // Semantic colors
  success: {
    light: '#4caf50',
    main: '#2e7d32',
    dark: '#1b5e20',
  },
  warning: {
    light: '#ff9800',
    main: '#ed6c02',
    dark: '#e65100',
  },
  danger: {
    light: '#ef5350',
    main: '#d32f2f',
    dark: '#c62828',
  },

  // Neutral colors
  neutral: {
    0: '#ffffff',
    50: '#fafafa',
    100: '#f5f5f5',
    200: '#eeeeee',
    300: '#e0e0e0',
    400: '#bdbdbd',
    500: '#9e9e9e',
    600: '#757575',
    700: '#616161',
    800: '#424242',
    900: '#212121',
  },
} as const;

export type ColorScale = keyof typeof colors;
export type PrimaryShade = keyof typeof colors.primary;
typescript
// quarks/colors.ts
export const colors = {
  // Brand colors
  primary: {
    50: '#e3f2fd',
    100: '#bbdefb',
    200: '#90caf9',
    300: '#64b5f6',
    400: '#42a5f5',
    500: '#2196f3', // Primary
    600: '#1e88e5',
    700: '#1976d2',
    800: '#1565c0',
    900: '#0d47a1',
  },

  // Semantic colors
  success: {
    light: '#4caf50',
    main: '#2e7d32',
    dark: '#1b5e20',
  },
  warning: {
    light: '#ff9800',
    main: '#ed6c02',
    dark: '#e65100',
  },
  danger: {
    light: '#ef5350',
    main: '#d32f2f',
    dark: '#c62828',
  },

  // Neutral colors
  neutral: {
    0: '#ffffff',
    50: '#fafafa',
    100: '#f5f5f5',
    200: '#eeeeee',
    300: '#e0e0e0',
    400: '#bdbdbd',
    500: '#9e9e9e',
    600: '#757575',
    700: '#616161',
    800: '#424242',
    900: '#212121',
  },
} as const;

export type ColorScale = keyof typeof colors;
export type PrimaryShade = keyof typeof colors.primary;

Spacing Tokens

间距令牌

typescript
// quarks/spacing.ts
export const spacing = {
  px: '1px',
  0: '0',
  0.5: '0.125rem', // 2px
  1: '0.25rem',    // 4px
  1.5: '0.375rem', // 6px
  2: '0.5rem',     // 8px
  2.5: '0.625rem', // 10px
  3: '0.75rem',    // 12px
  3.5: '0.875rem', // 14px
  4: '1rem',       // 16px
  5: '1.25rem',    // 20px
  6: '1.5rem',     // 24px
  7: '1.75rem',    // 28px
  8: '2rem',       // 32px
  9: '2.25rem',    // 36px
  10: '2.5rem',    // 40px
  12: '3rem',      // 48px
  14: '3.5rem',    // 56px
  16: '4rem',      // 64px
  20: '5rem',      // 80px
  24: '6rem',      // 96px
} as const;

// Semantic spacing aliases
export const spacingAliases = {
  none: spacing[0],
  xs: spacing[1],
  sm: spacing[2],
  md: spacing[4],
  lg: spacing[6],
  xl: spacing[8],
  '2xl': spacing[12],
  '3xl': spacing[16],
} as const;
typescript
// quarks/spacing.ts
export const spacing = {
  px: '1px',
  0: '0',
  0.5: '0.125rem', // 2px
  1: '0.25rem',    // 4px
  1.5: '0.375rem', // 6px
  2: '0.5rem',     // 8px
  2.5: '0.625rem', // 10px
  3: '0.75rem',    // 12px
  3.5: '0.875rem', // 14px
  4: '1rem',       // 16px
  5: '1.25rem',    // 20px
  6: '1.5rem',     // 24px
  7: '1.75rem',    // 28px
  8: '2rem',       // 32px
  9: '2.25rem',    // 36px
  10: '2.5rem',    // 40px
  12: '3rem',      // 48px
  14: '3.5rem',    // 56px
  16: '4rem',      // 64px
  20: '5rem',      // 80px
  24: '6rem',      // 96px
} as const;

// Semantic spacing aliases
export const spacingAliases = {
  none: spacing[0],
  xs: spacing[1],
  sm: spacing[2],
  md: spacing[4],
  lg: spacing[6],
  xl: spacing[8],
  '2xl': spacing[12],
  '3xl': spacing[16],
} as const;

Typography Tokens

排版令牌

typescript
// quarks/typography.ts
export const fontFamily = {
  sans: [
    'Inter',
    'ui-sans-serif',
    'system-ui',
    '-apple-system',
    'BlinkMacSystemFont',
    'Segoe UI',
    'Roboto',
    'sans-serif',
  ].join(', '),
  serif: [
    'Georgia',
    'Cambria',
    'Times New Roman',
    'Times',
    'serif',
  ].join(', '),
  mono: [
    'Fira Code',
    'ui-monospace',
    'SFMono-Regular',
    'Menlo',
    'Monaco',
    'Consolas',
    'monospace',
  ].join(', '),
} as const;

export const 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
} as const;

export const fontWeight = {
  thin: 100,
  extralight: 200,
  light: 300,
  normal: 400,
  medium: 500,
  semibold: 600,
  bold: 700,
  extrabold: 800,
  black: 900,
} as const;

export const lineHeight = {
  none: 1,
  tight: 1.25,
  snug: 1.375,
  normal: 1.5,
  relaxed: 1.625,
  loose: 2,
} as const;

export const letterSpacing = {
  tighter: '-0.05em',
  tight: '-0.025em',
  normal: '0em',
  wide: '0.025em',
  wider: '0.05em',
  widest: '0.1em',
} as const;
typescript
// quarks/typography.ts
export const fontFamily = {
  sans: [
    'Inter',
    'ui-sans-serif',
    'system-ui',
    '-apple-system',
    'BlinkMacSystemFont',
    'Segoe UI',
    'Roboto',
    'sans-serif',
  ].join(', '),
  serif: [
    'Georgia',
    'Cambria',
    'Times New Roman',
    'Times',
    'serif',
  ].join(', '),
  mono: [
    'Fira Code',
    'ui-monospace',
    'SFMono-Regular',
    'Menlo',
    'Monaco',
    'Consolas',
    'monospace',
  ].join(', '),
} as const;

export const 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
} as const;

export const fontWeight = {
  thin: 100,
  extralight: 200,
  light: 300,
  normal: 400,
  medium: 500,
  semibold: 600,
  bold: 700,
  extrabold: 800,
  black: 900,
} as const;

export const lineHeight = {
  none: 1,
  tight: 1.25,
  snug: 1.375,
  normal: 1.5,
  relaxed: 1.625,
  loose: 2,
} as const;

export const letterSpacing = {
  tighter: '-0.05em',
  tight: '-0.025em',
  normal: '0em',
  wide: '0.025em',
  wider: '0.05em',
  widest: '0.1em',
} as const;

Border Tokens

边框令牌

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

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

export const borderWidth = {
  DEFAULT: '1px',
  0: '0',
  2: '2px',
  4: '4px',
  8: '8px',
} as const;

Shadow Tokens

阴影令牌

typescript
// quarks/shadows.ts
export const shadows = {
  none: 'none',
  sm: '0 1px 2px 0 rgb(0 0 0 / 0.05)',
  DEFAULT: '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)',
} as const;

// Focus ring shadows
export const focusRings = {
  primary: '0 0 0 3px rgba(33, 150, 243, 0.4)',
  danger: '0 0 0 3px rgba(239, 83, 80, 0.4)',
  success: '0 0 0 3px rgba(76, 175, 80, 0.4)',
} as const;
typescript
// quarks/shadows.ts
export const shadows = {
  none: 'none',
  sm: '0 1px 2px 0 rgb(0 0 0 / 0.05)',
  DEFAULT: '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)',
} as const;

// Focus ring shadows
export const focusRings = {
  primary: '0 0 0 3px rgba(33, 150, 243, 0.4)',
  danger: '0 0 0 3px rgba(239, 83, 80, 0.4)',
  success: '0 0 0 3px rgba(76, 175, 80, 0.4)',
} as const;

Animation Tokens

动画令牌

typescript
// quarks/animations.ts
export const duration = {
  instant: '0ms',
  fast: '100ms',
  normal: '200ms',
  slow: '300ms',
  slower: '500ms',
} as const;

export const easing = {
  linear: 'linear',
  in: 'cubic-bezier(0.4, 0, 1, 1)',
  out: 'cubic-bezier(0, 0, 0.2, 1)',
  inOut: 'cubic-bezier(0.4, 0, 0.2, 1)',
  bounce: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)',
} as const;

export const transitions = {
  none: 'none',
  all: `all ${duration.normal} ${easing.inOut}`,
  colors: `background-color, border-color, color, fill, stroke ${duration.normal} ${easing.inOut}`,
  opacity: `opacity ${duration.normal} ${easing.inOut}`,
  shadow: `box-shadow ${duration.normal} ${easing.inOut}`,
  transform: `transform ${duration.normal} ${easing.inOut}`,
} as const;
typescript
// quarks/animations.ts
export const duration = {
  instant: '0ms',
  fast: '100ms',
  normal: '200ms',
  slow: '300ms',
  slower: '500ms',
} as const;

export const easing = {
  linear: 'linear',
  in: 'cubic-bezier(0.4, 0, 1, 1)',
  out: 'cubic-bezier(0, 0, 0.2, 1)',
  inOut: 'cubic-bezier(0.4, 0, 0.2, 1)',
  bounce: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)',
} as const;

export const transitions = {
  none: 'none',
  all: `all ${duration.normal} ${easing.inOut}`,
  colors: `background-color, border-color, color, fill, stroke ${duration.normal} ${easing.inOut}`,
  opacity: `opacity ${duration.normal} ${easing.inOut}`,
  shadow: `box-shadow ${duration.normal} ${easing.inOut}`,
  transform: `transform ${duration.normal} ${easing.inOut}`,
} as const;

Breakpoint Tokens

断点令牌

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

// Media query helpers
export const mediaQueries = {
  xs: `@media (min-width: ${breakpoints.xs})`,
  sm: `@media (min-width: ${breakpoints.sm})`,
  md: `@media (min-width: ${breakpoints.md})`,
  lg: `@media (min-width: ${breakpoints.lg})`,
  xl: `@media (min-width: ${breakpoints.xl})`,
  '2xl': `@media (min-width: ${breakpoints['2xl']})`,
} as const;
typescript
// quarks/breakpoints.ts
export const breakpoints = {
  xs: '320px',
  sm: '640px',
  md: '768px',
  lg: '1024px',
  xl: '1280px',
  '2xl': '1536px',
} as const;

// Media query helpers
export const mediaQueries = {
  xs: `@media (min-width: ${breakpoints.xs})`,
  sm: `@media (min-width: ${breakpoints.sm})`,
  md: `@media (min-width: ${breakpoints.md})`,
  lg: `@media (min-width: ${breakpoints.lg})`,
  xl: `@media (min-width: ${breakpoints.xl})`,
  '2xl': `@media (min-width: ${breakpoints['2xl']})`,
} as const;

Z-Index Tokens

Z轴层级令牌

typescript
// quarks/z-index.ts
export const zIndex = {
  hide: -1,
  base: 0,
  raised: 1,
  dropdown: 1000,
  sticky: 1100,
  fixed: 1200,
  overlay: 1300,
  modal: 1400,
  popover: 1500,
  tooltip: 1600,
  toast: 1700,
} as const;
typescript
// quarks/z-index.ts
export const zIndex = {
  hide: -1,
  base: 0,
  raised: 1,
  dropdown: 1000,
  sticky: 1100,
  fixed: 1200,
  overlay: 1300,
  modal: 1400,
  popover: 1500,
  tooltip: 1600,
  toast: 1700,
} as const;

CSS Custom Properties

CSS自定义属性

Export quarks as CSS custom properties for runtime theming.
typescript
// quarks/css-variables.ts
import { colors, spacing, fontSize, fontFamily } from './index';

export function generateCSSVariables(): string {
  const lines: string[] = [':root {'];

  // Colors
  Object.entries(colors).forEach(([category, shades]) => {
    if (typeof shades === 'object') {
      Object.entries(shades).forEach(([shade, value]) => {
        lines.push(`  --color-${category}-${shade}: ${value};`);
      });
    }
  });

  // Spacing
  Object.entries(spacing).forEach(([key, value]) => {
    const sanitizedKey = key.replace('.', '_');
    lines.push(`  --spacing-${sanitizedKey}: ${value};`);
  });

  // Typography
  Object.entries(fontSize).forEach(([key, value]) => {
    lines.push(`  --font-size-${key}: ${value};`);
  });

  Object.entries(fontFamily).forEach(([key, value]) => {
    lines.push(`  --font-family-${key}: ${value};`);
  });

  lines.push('}');
  return lines.join('\n');
}
将Quarks导出为CSS自定义属性,用于运行时主题切换。
typescript
// quarks/css-variables.ts
import { colors, spacing, fontSize, fontFamily } from './index';

export function generateCSSVariables(): string {
  const lines: string[] = [':root {'];

  // Colors
  Object.entries(colors).forEach(([category, shades]) => {
    if (typeof shades === 'object') {
      Object.entries(shades).forEach(([shade, value]) => {
        lines.push(`  --color-${category}-${shade}: ${value};`);
      });
    }
  });

  // Spacing
  Object.entries(spacing).forEach(([key, value]) => {
    const sanitizedKey = key.replace('.', '_');
    lines.push(`  --spacing-${sanitizedKey}: ${value};`);
  });

  // Typography
  Object.entries(fontSize).forEach(([key, value]) => {
    lines.push(`  --font-size-${key}: ${value};`);
  });

  Object.entries(fontFamily).forEach(([key, value]) => {
    lines.push(`  --font-family-${key}: ${value};`);
  });

  lines.push('}');
  return lines.join('\n');
}

Generated CSS

生成的CSS

css
/* quarks/variables.css - Generated output */
:root {
  /* Colors - Primary */
  --color-primary-50: #e3f2fd;
  --color-primary-100: #bbdefb;
  --color-primary-500: #2196f3;
  --color-primary-900: #0d47a1;

  /* Colors - Neutral */
  --color-neutral-0: #ffffff;
  --color-neutral-100: #f5f5f5;
  --color-neutral-900: #212121;

  /* Spacing */
  --spacing-0: 0;
  --spacing-1: 0.25rem;
  --spacing-2: 0.5rem;
  --spacing-4: 1rem;
  --spacing-8: 2rem;

  /* Typography */
  --font-size-xs: 0.75rem;
  --font-size-sm: 0.875rem;
  --font-size-base: 1rem;
  --font-size-lg: 1.125rem;

  --font-family-sans: Inter, ui-sans-serif, system-ui, sans-serif;
  --font-family-mono: Fira Code, ui-monospace, monospace;

  /* Borders */
  --border-radius-sm: 0.125rem;
  --border-radius-md: 0.375rem;
  --border-radius-lg: 0.5rem;
  --border-radius-full: 9999px;

  /* Shadows */
  --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
  --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
  --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);

  /* Transitions */
  --duration-fast: 100ms;
  --duration-normal: 200ms;
  --duration-slow: 300ms;
  --easing-in-out: cubic-bezier(0.4, 0, 0.2, 1);
}
css
/* quarks/variables.css - Generated output */
:root {
  /* Colors - Primary */
  --color-primary-50: #e3f2fd;
  --color-primary-100: #bbdefb;
  --color-primary-500: #2196f3;
  --color-primary-900: #0d47a1;

  /* Colors - Neutral */
  --color-neutral-0: #ffffff;
  --color-neutral-100: #f5f5f5;
  --color-neutral-900: #212121;

  /* Spacing */
  --spacing-0: 0;
  --spacing-1: 0.25rem;
  --spacing-2: 0.5rem;
  --spacing-4: 1rem;
  --spacing-8: 2rem;

  /* Typography */
  --font-size-xs: 0.75rem;
  --font-size-sm: 0.875rem;
  --font-size-base: 1rem;
  --font-size-lg: 1.125rem;

  --font-family-sans: Inter, ui-sans-serif, system-ui, sans-serif;
  --font-family-mono: Fira Code, ui-monospace, monospace;

  /* Borders */
  --border-radius-sm: 0.125rem;
  --border-radius-md: 0.375rem;
  --border-radius-lg: 0.5rem;
  --border-radius-full: 9999px;

  /* Shadows */
  --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
  --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
  --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);

  /* Transitions */
  --duration-fast: 100ms;
  --duration-normal: 200ms;
  --duration-slow: 300ms;
  --easing-in-out: cubic-bezier(0.4, 0, 0.2, 1);
}

Directory Structure

目录结构

Recommended Structure

推荐结构

text
src/
  quarks/                    # Design tokens
    index.ts                 # Barrel export
    colors.ts
    spacing.ts
    typography.ts
    borders.ts
    shadows.ts
    animations.ts
    breakpoints.ts
    z-index.ts
    css-variables.ts         # CSS custom property generator
    variables.css            # Generated CSS file
  components/
    atoms/                   # Atoms that consume quarks
      Button/
      Input/
    molecules/
    organisms/
    templates/
    pages/
text
src/
  quarks/                    # 设计令牌
    index.ts                 # 桶导出文件
    colors.ts
    spacing.ts
    typography.ts
    borders.ts
    shadows.ts
    animations.ts
    breakpoints.ts
    z-index.ts
    css-variables.ts         # CSS自定义属性生成器
    variables.css            # 生成的CSS文件
  components/
    atoms/                   # 依赖Quarks的原子组件
      Button/
      Input/
    molecules/
    organisms/
    templates/
    pages/

Index Barrel Export

索引桶导出

typescript
// quarks/index.ts
export * from './colors';
export * from './spacing';
export * from './typography';
export * from './borders';
export * from './shadows';
export * from './animations';
export * from './breakpoints';
export * from './z-index';
typescript
// quarks/index.ts
export * from './colors';
export * from './spacing';
export * from './typography';
export * from './borders';
export * from './shadows';
export * from './animations';
export * from './breakpoints';
export * from './z-index';

Using Quarks in Atoms

在原子组件中使用Quarks

CSS-in-JS (styled-components/emotion)

CSS-in-JS(styled-components/emotion)

typescript
// atoms/Button/Button.tsx
import styled from 'styled-components';
import { colors, spacing, fontSize, borderRadius, transitions } from '@/quarks';

export const Button = styled.button<{ variant?: 'primary' | 'secondary' }>`
  padding: ${spacing[2]} ${spacing[4]};
  font-size: ${fontSize.base};
  border-radius: ${borderRadius.md};
  transition: ${transitions.colors};

  ${({ variant = 'primary' }) =>
    variant === 'primary'
      ? `
        background-color: ${colors.primary[500]};
        color: ${colors.neutral[0]};

        &:hover {
          background-color: ${colors.primary[600]};
        }
      `
      : `
        background-color: transparent;
        color: ${colors.primary[500]};
        border: 1px solid ${colors.primary[500]};

        &:hover {
          background-color: ${colors.primary[50]};
        }
      `}
`;
typescript
// atoms/Button/Button.tsx
import styled from 'styled-components';
import { colors, spacing, fontSize, borderRadius, transitions } from '@/quarks';

export const Button = styled.button<{ variant?: 'primary' | 'secondary' }>`
  padding: ${spacing[2]} ${spacing[4]};
  font-size: ${fontSize.base};
  border-radius: ${borderRadius.md};
  transition: ${transitions.colors};

  ${({ variant = 'primary' }) =>
    variant === 'primary'
      ? `
        background-color: ${colors.primary[500]};
        color: ${colors.neutral[0]};

        &:hover {
          background-color: ${colors.primary[600]};
        }
      `
      : `
        background-color: transparent;
        color: ${colors.primary[500]};
        border: 1px solid ${colors.primary[500]};

        &:hover {
          background-color: ${colors.primary[50]};
        }
      `}
`;

CSS Modules with Variables

结合变量使用CSS Modules

tsx
// atoms/Button/Button.tsx
import styles from './Button.module.css';

export const Button = ({ variant = 'primary', children }) => (
  <button className={`${styles.button} ${styles[variant]}`}>
    {children}
  </button>
);
css
/* atoms/Button/Button.module.css */
.button {
  padding: var(--spacing-2) var(--spacing-4);
  font-size: var(--font-size-base);
  border-radius: var(--border-radius-md);
  transition: var(--duration-normal) var(--easing-in-out);
}

.primary {
  background-color: var(--color-primary-500);
  color: var(--color-neutral-0);
}

.primary:hover {
  background-color: var(--color-primary-600);
}

.secondary {
  background-color: transparent;
  color: var(--color-primary-500);
  border: 1px solid var(--color-primary-500);
}
tsx
// atoms/Button/Button.tsx
import styles from './Button.module.css';

export const Button = ({ variant = 'primary', children }) => (
  <button className={`${styles.button} ${styles[variant]}`}>
    {children}
  </button>
);
css
/* atoms/Button/Button.module.css */
.button {
  padding: var(--spacing-2) var(--spacing-4);
  font-size: var(--font-size-base);
  border-radius: var(--border-radius-md);
  transition: var(--duration-normal) var(--easing-in-out);
}

.primary {
  background-color: var(--color-primary-500);
  color: var(--color-neutral-0);
}

.primary:hover {
  background-color: var(--color-primary-600);
}

.secondary {
  background-color: transparent;
  color: var(--color-primary-500);
  border: 1px solid var(--color-primary-500);
}

Tailwind CSS Integration

Tailwind CSS集成

javascript
// tailwind.config.js
const { colors, spacing, fontSize, borderRadius } = require('./src/quarks');

module.exports = {
  theme: {
    colors,
    spacing,
    fontSize,
    borderRadius,
  },
};
javascript
// tailwind.config.js
const { colors, spacing, fontSize, borderRadius } = require('./src/quarks');

module.exports = {
  theme: {
    colors,
    spacing,
    fontSize,
    borderRadius,
  },
};

Theming with Quarks

使用Quarks实现主题定制

Dark Mode Support

暗黑模式支持

typescript
// quarks/themes.ts
import { colors } from './colors';

export const lightTheme = {
  background: colors.neutral[0],
  foreground: colors.neutral[900],
  muted: colors.neutral[100],
  mutedForeground: colors.neutral[500],
  primary: colors.primary[500],
  primaryForeground: colors.neutral[0],
};

export const darkTheme = {
  background: colors.neutral[900],
  foreground: colors.neutral[0],
  muted: colors.neutral[800],
  mutedForeground: colors.neutral[400],
  primary: colors.primary[400],
  primaryForeground: colors.neutral[900],
};

export type Theme = typeof lightTheme;
typescript
// quarks/themes.ts
import { colors } from './colors';

export const lightTheme = {
  background: colors.neutral[0],
  foreground: colors.neutral[900],
  muted: colors.neutral[100],
  mutedForeground: colors.neutral[500],
  primary: colors.primary[500],
  primaryForeground: colors.neutral[0],
};

export const darkTheme = {
  background: colors.neutral[900],
  foreground: colors.neutral[0],
  muted: colors.neutral[800],
  mutedForeground: colors.neutral[400],
  primary: colors.primary[400],
  primaryForeground: colors.neutral[900],
};

export type Theme = typeof lightTheme;

CSS Variables for Theming

用于主题定制的CSS变量

css
/* quarks/theme-light.css */
:root {
  --background: var(--color-neutral-0);
  --foreground: var(--color-neutral-900);
  --muted: var(--color-neutral-100);
  --primary: var(--color-primary-500);
}

/* quarks/theme-dark.css */
[data-theme="dark"] {
  --background: var(--color-neutral-900);
  --foreground: var(--color-neutral-0);
  --muted: var(--color-neutral-800);
  --primary: var(--color-primary-400);
}
css
/* quarks/theme-light.css */
:root {
  --background: var(--color-neutral-0);
  --foreground: var(--color-neutral-900);
  --muted: var(--color-neutral-100);
  --primary: var(--color-primary-500);
}

/* quarks/theme-dark.css */
[data-theme="dark"] {
  --background: var(--color-neutral-900);
  --foreground: var(--color-neutral-0);
  --muted: var(--color-neutral-800);
  --primary: var(--color-primary-400);
}

Best Practices

最佳实践

1. Use Semantic Names

1. 使用语义化命名

typescript
// GOOD: Semantic naming
export const colors = {
  primary: { ... },
  danger: { ... },
  success: { ... },
};

// BAD: Raw color names
export const colors = {
  blue: { ... },
  red: { ... },
  green: { ... },
};
typescript
// 推荐:语义化命名
export const colors = {
  primary: { ... },
  danger: { ... },
  success: { ... },
};

// 不推荐:原始颜色命名
export const colors = {
  blue: { ... },
  red: { ... },
  green: { ... },
};

2. Consistent Scales

2. 保持一致的刻度

typescript
// GOOD: Consistent scale pattern
export const colors = {
  primary: { 50, 100, 200, 300, 400, 500, 600, 700, 800, 900 },
  secondary: { 50, 100, 200, 300, 400, 500, 600, 700, 800, 900 },
};

// BAD: Inconsistent scales
export const colors = {
  primary: { light, medium, dark },
  secondary: { 100, 500, 900 },
};
typescript
// 推荐:一致的刻度模式
export const colors = {
  primary: { 50, 100, 200, 300, 400, 500, 600, 700, 800, 900 },
  secondary: { 50, 100, 200, 300, 400, 500, 600, 700, 800, 900 },
};

// 不推荐:不一致的刻度
export const colors = {
  primary: { light, medium, dark },
  secondary: { 100, 500, 900 },
};

3. Type Safety

3. 类型安全

typescript
// GOOD: Type-safe tokens
export const spacing = { ... } as const;
export type SpacingKey = keyof typeof spacing;

function getSpacing(key: SpacingKey): string {
  return spacing[key];
}

// BAD: Untyped strings
function getSpacing(key: string): string {
  return spacing[key]; // No type checking
}
typescript
// 推荐:类型安全的令牌
export const spacing = { ... } as const;
export type SpacingKey = keyof typeof spacing;

function getSpacing(key: SpacingKey): string {
  return spacing[key];
}

// 不推荐:无类型检查的字符串参数
function getSpacing(key: string): string {
  return spacing[key]; // 无类型检查
}

4. Single Source of Truth

4. 单一数据源

typescript
// GOOD: Quarks as the only source
// All components import from quarks
import { colors } from '@/quarks';

// BAD: Duplicate values
const Button = styled.button`
  color: #2196f3; // Hardcoded, not from quarks!
`;
typescript
// 推荐:仅从Quarks获取值
// 所有组件都从Quarks导入
import { colors } from '@/quarks';

// 不推荐:硬编码重复值
const Button = styled.button`
  color: #2196f3; // 硬编码,未使用Quarks的值!
`;

5. Documentation

5. 添加文档注释

typescript
// GOOD: Document your tokens
/**
 * Primary color scale
 * Use 500 for default primary actions
 * Use 600+ for hover/active states
 * Use 100-400 for backgrounds/accents
 */
export const primary = { ... };
typescript
// 推荐:为令牌添加文档
/**
 * 主色调刻度
 * 默认主操作使用500色值
 * 悬停/激活状态使用600+色值
 * 背景/强调色使用100-400色值
 */
export const primary = { ... };

Anti-Patterns to Avoid

需要避免的反模式

1. Quarks with Logic

1. 在Quarks中加入逻辑

typescript
// BAD: Quarks should be pure values
export const getColor = (isDark: boolean) =>
  isDark ? '#ffffff' : '#000000';

// GOOD: Pure values, logic in components/themes
export const colors = {
  light: { text: '#000000' },
  dark: { text: '#ffffff' },
};
typescript
// 不推荐:Quarks应该是纯值
export const getColor = (isDark: boolean) =>
  isDark ? '#ffffff' : '#000000';

// 推荐:纯值,逻辑放在组件/主题中
export const colors = {
  light: { text: '#000000' },
  dark: { text: '#ffffff' },
};

2. Component-Specific Tokens

2. 为特定组件创建令牌

typescript
// BAD: Token for specific component
export const buttonPrimaryBackground = '#2196f3';

// GOOD: Generic semantic tokens
export const colors = {
  primary: { 500: '#2196f3' },
};
typescript
// 不推荐:为特定组件创建的令牌
export const buttonPrimaryBackground = '#2196f3';

// 推荐:通用语义化令牌
export const colors = {
  primary: { 500: '#2196f3' },
};

3. Too Many Tokens

3. 令牌数量过多

typescript
// BAD: Token for every possible value
export const spacing = {
  1: '1px',
  2: '2px',
  3: '3px',
  4: '4px',
  5: '5px',
  // ... 100 more values
};

// GOOD: Deliberate, usable scale
export const spacing = {
  1: '0.25rem',
  2: '0.5rem',
  4: '1rem',
  6: '1.5rem',
  8: '2rem',
};
typescript
// 不推荐:为每个可能的值创建令牌
export const spacing = {
  1: '1px',
  2: '2px',
  3: '3px',
  4: '4px',
  5: '5px',
  // ... 还有100个值
};

// 推荐:精简、实用的刻度
export const spacing = {
  1: '0.25rem',
  2: '0.5rem',
  4: '1rem',
  6: '1.5rem',
  8: '2rem',
};

When to Use This Skill

何时使用此技能

  • Setting up design tokens for a new design system
  • Organizing existing CSS variables into a structured system
  • Creating theme-able component libraries
  • Ensuring consistency across a large codebase
  • Building white-label or multi-brand applications
  • Migrating from hardcoded values to design tokens
  • 为新设计系统搭建设计令牌
  • 将现有CSS变量整理为结构化系统
  • 创建支持主题切换的组件库
  • 确保大型代码库的样式一致性
  • 构建白标或多品牌应用
  • 从硬编码值迁移到设计令牌

Related Skills

相关技能

  • atomic-design-fundamentals
    - Core methodology overview
  • atomic-design-atoms
    - Creating atomic components that consume quarks
  • atomic-design-integration
    - Framework-specific implementation patterns
  • atomic-design-fundamentals
    - 核心方法论概述
  • atomic-design-atoms
    - 创建依赖Quarks的原子组件
  • atomic-design-integration
    - 框架特定的实现模式

Resources

参考资源

Documentation

文档

Tools

工具