design-tokens

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Design Tokens

Design Tokens

Design token architecture, CSS custom properties, and theme system implementation.
Design Token架构、CSS自定义属性以及主题系统实现方案。

Core Expertise

核心能力

  • Token Architecture: Organizing design tokens for scalability
  • CSS Custom Properties: Variable patterns and inheritance
  • Theme Systems: Light/dark mode, user preferences
  • Component Integration: Applying tokens consistently
  • Token架构:可扩展的Design Token组织方案
  • CSS自定义属性:变量模式与继承机制
  • 主题系统:亮色/暗黑模式、用户偏好设置
  • 组件集成:Token的一致性应用

Token Structure

Token结构

Three-Tier Architecture

三层架构

css
/* 1. Primitive tokens (raw values) */
:root {
  --color-blue-50: #eff6ff;
  --color-blue-100: #dbeafe;
  --color-blue-500: #3b82f6;
  --color-blue-600: #2563eb;
  --color-blue-700: #1d4ed8;

  --spacing-1: 0.25rem;
  --spacing-2: 0.5rem;
  --spacing-4: 1rem;
  --spacing-8: 2rem;

  --font-size-sm: 0.875rem;
  --font-size-base: 1rem;
  --font-size-lg: 1.125rem;
}

/* 2. Semantic tokens (purpose-based) */
:root {
  --color-primary: var(--color-blue-600);
  --color-primary-hover: var(--color-blue-700);
  --color-background: white;
  --color-surface: var(--color-gray-50);
  --color-text: var(--color-gray-900);
  --color-text-muted: var(--color-gray-600);

  --spacing-component: var(--spacing-4);
  --spacing-section: var(--spacing-8);
}

/* 3. Component tokens (specific usage) */
.button {
  --button-padding-x: var(--spacing-4);
  --button-padding-y: var(--spacing-2);
  --button-bg: var(--color-primary);
  --button-bg-hover: var(--color-primary-hover);
  --button-text: white;

  padding: var(--button-padding-y) var(--button-padding-x);
  background: var(--button-bg);
  color: var(--button-text);
}

.button:hover {
  background: var(--button-bg-hover);
}
css
/* 1. Primitive tokens (raw values) */
:root {
  --color-blue-50: #eff6ff;
  --color-blue-100: #dbeafe;
  --color-blue-500: #3b82f6;
  --color-blue-600: #2563eb;
  --color-blue-700: #1d4ed8;

  --spacing-1: 0.25rem;
  --spacing-2: 0.5rem;
  --spacing-4: 1rem;
  --spacing-8: 2rem;

  --font-size-sm: 0.875rem;
  --font-size-base: 1rem;
  --font-size-lg: 1.125rem;
}

/* 2. Semantic tokens (purpose-based) */
:root {
  --color-primary: var(--color-blue-600);
  --color-primary-hover: var(--color-blue-700);
  --color-background: white;
  --color-surface: var(--color-gray-50);
  --color-text: var(--color-gray-900);
  --color-text-muted: var(--color-gray-600);

  --spacing-component: var(--spacing-4);
  --spacing-section: var(--spacing-8);
}

/* 3. Component tokens (specific usage) */
.button {
  --button-padding-x: var(--spacing-4);
  --button-padding-y: var(--spacing-2);
  --button-bg: var(--color-primary);
  --button-bg-hover: var(--color-primary-hover);
  --button-text: white;

  padding: var(--button-padding-y) var(--button-padding-x);
  background: var(--button-bg);
  color: var(--button-text);
}

.button:hover {
  background: var(--button-bg-hover);
}

Token Categories

Token分类

css
:root {
  /* Colors */
  --color-{name}-{shade}: value;

  /* Typography */
  --font-family-{name}: value;
  --font-size-{name}: value;
  --font-weight-{name}: value;
  --line-height-{name}: value;
  --letter-spacing-{name}: value;

  /* Spacing */
  --spacing-{scale}: value;

  /* Sizing */
  --size-{name}: value;

  /* Borders */
  --border-width-{name}: value;
  --border-radius-{name}: value;

  /* Shadows */
  --shadow-{name}: value;

  /* Transitions */
  --duration-{name}: value;
  --easing-{name}: value;

  /* Z-index */
  --z-{name}: value;
}
css
:root {
  /* Colors */
  --color-{name}-{shade}: value;

  /* Typography */
  --font-family-{name}: value;
  --font-size-{name}: value;
  --font-weight-{name}: value;
  --line-height-{name}: value;
  --letter-spacing-{name}: value;

  /* Spacing */
  --spacing-{scale}: value;

  /* Sizing */
  --size-{name}: value;

  /* Borders */
  --border-width-{name}: value;
  --border-radius-{name}: value;

  /* Shadows */
  --shadow-{name}: value;

  /* Transitions */
  --duration-{name}: value;
  --easing-{name}: value;

  /* Z-index */
  --z-{name}: value;
}

Theme Implementation

主题实现

Light/Dark Mode

亮色/暗黑模式

css
/* Default (light) theme */
:root {
  --color-background: #ffffff;
  --color-surface: #f9fafb;
  --color-text: #111827;
  --color-text-muted: #6b7280;
  --color-border: #e5e7eb;
}

/* Dark theme */
[data-theme="dark"] {
  --color-background: #111827;
  --color-surface: #1f2937;
  --color-text: #f9fafb;
  --color-text-muted: #9ca3af;
  --color-border: #374151;
}

/* System preference */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --color-background: #111827;
    --color-surface: #1f2937;
    --color-text: #f9fafb;
    --color-text-muted: #9ca3af;
    --color-border: #374151;
  }
}
css
/* Default (light) theme */
:root {
  --color-background: #ffffff;
  --color-surface: #f9fafb;
  --color-text: #111827;
  --color-text-muted: #6b7280;
  --color-border: #e5e7eb;
}

/* Dark theme */
[data-theme="dark"] {
  --color-background: #111827;
  --color-surface: #1f2937;
  --color-text: #f9fafb;
  --color-text-muted: #9ca3af;
  --color-border: #374151;
}

/* System preference */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --color-background: #111827;
    --color-surface: #1f2937;
    --color-text: #f9fafb;
    --color-text-muted: #9ca3af;
    --color-border: #374151;
  }
}

Theme Switching (JavaScript)

主题切换(JavaScript)

typescript
type Theme = 'light' | 'dark' | 'system';

function setTheme(theme: Theme) {
  const root = document.documentElement;

  if (theme === 'system') {
    root.removeAttribute('data-theme');
    localStorage.removeItem('theme');
  } else {
    root.setAttribute('data-theme', theme);
    localStorage.setItem('theme', theme);
  }
}

function getTheme(): Theme {
  return (localStorage.getItem('theme') as Theme) || 'system';
}

// Initialize on page load
function initTheme() {
  const saved = localStorage.getItem('theme');
  if (saved === 'light' || saved === 'dark') {
    document.documentElement.setAttribute('data-theme', saved);
  }
}

// Add to <head> to prevent flash
// <script>
//   (function() {
//     var t = localStorage.getItem('theme');
//     if (t === 'light' || t === 'dark') {
//       document.documentElement.setAttribute('data-theme', t);
//     }
//   })();
// </script>
typescript
type Theme = 'light' | 'dark' | 'system';

function setTheme(theme: Theme) {
  const root = document.documentElement;

  if (theme === 'system') {
    root.removeAttribute('data-theme');
    localStorage.removeItem('theme');
  } else {
    root.setAttribute('data-theme', theme);
    localStorage.setItem('theme', theme);
  }
}

function getTheme(): Theme {
  return (localStorage.getItem('theme') as Theme) || 'system';
}

// Initialize on page load
function initTheme() {
  const saved = localStorage.getItem('theme');
  if (saved === 'light' || saved === 'dark') {
    document.documentElement.setAttribute('data-theme', saved);
  }
}

// Add to <head> to prevent flash
// <script>
//   (function() {
//     var t = localStorage.getItem('theme');
//     if (t === 'light' || t === 'dark') {
//       document.documentElement.setAttribute('data-theme', t);
//     }
//   })();
// </script>

React Theme Context

React主题上下文

typescript
import { createContext, useContext, useEffect, useState } from 'react';

type Theme = 'light' | 'dark' | 'system';

interface ThemeContextType {
  theme: Theme;
  setTheme: (theme: Theme) => void;
  resolvedTheme: 'light' | 'dark';
}

const ThemeContext = createContext<ThemeContextType | null>(null);

export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setThemeState] = useState<Theme>('system');
  const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>('light');

  useEffect(() => {
    const saved = localStorage.getItem('theme') as Theme;
    if (saved) setThemeState(saved);
  }, []);

  useEffect(() => {
    const root = document.documentElement;

    if (theme === 'system') {
      root.removeAttribute('data-theme');
      const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
      setResolvedTheme(isDark ? 'dark' : 'light');
    } else {
      root.setAttribute('data-theme', theme);
      setResolvedTheme(theme);
    }
  }, [theme]);

  const setTheme = (newTheme: Theme) => {
    setThemeState(newTheme);
    if (newTheme === 'system') {
      localStorage.removeItem('theme');
    } else {
      localStorage.setItem('theme', newTheme);
    }
  };

  return (
    <ThemeContext.Provider value={{ theme, setTheme, resolvedTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) throw new Error('useTheme must be used within ThemeProvider');
  return context;
}
typescript
import { createContext, useContext, useEffect, useState } from 'react';

type Theme = 'light' | 'dark' | 'system';

interface ThemeContextType {
  theme: Theme;
  setTheme: (theme: Theme) => void;
  resolvedTheme: 'light' | 'dark';
}

const ThemeContext = createContext<ThemeContextType | null>(null);

export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setThemeState] = useState<Theme>('system');
  const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>('light');

  useEffect(() => {
    const saved = localStorage.getItem('theme') as Theme;
    if (saved) setThemeState(saved);
  }, []);

  useEffect(() => {
    const root = document.documentElement;

    if (theme === 'system') {
      root.removeAttribute('data-theme');
      const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
      setResolvedTheme(isDark ? 'dark' : 'light');
    } else {
      root.setAttribute('data-theme', theme);
      setResolvedTheme(theme);
    }
  }, [theme]);

  const setTheme = (newTheme: Theme) => {
    setThemeState(newTheme);
    if (newTheme === 'system') {
      localStorage.removeItem('theme');
    } else {
      localStorage.setItem('theme', newTheme);
    }
  };

  return (
    <ThemeContext.Provider value={{ theme, setTheme, resolvedTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) throw new Error('useTheme must be used within ThemeProvider');
  return context;
}

File Organization

文件组织

Recommended Structure

推荐结构

styles/
├── tokens/
│   ├── primitives.css      # Raw values
│   ├── semantic.css        # Purpose-based tokens
│   └── index.css           # Combines all tokens
├── themes/
│   ├── light.css           # Light theme overrides
│   └── dark.css            # Dark theme overrides
├── base/
│   ├── reset.css           # CSS reset
│   └── typography.css      # Base typography
└── components/
    ├── button.css
    └── card.css
styles/
├── tokens/
│   ├── primitives.css      # Raw values
│   ├── semantic.css        # Purpose-based tokens
│   └── index.css           # Combines all tokens
├── themes/
│   ├── light.css           # Light theme overrides
│   └── dark.css            # Dark theme overrides
├── base/
│   ├── reset.css           # CSS reset
│   └── typography.css      # Base typography
└── components/
    ├── button.css
    └── card.css

JSON Token Format (for tooling)

JSON格式Token(用于工具链)

json
{
  "color": {
    "primary": {
      "value": "#3b82f6",
      "type": "color",
      "description": "Primary brand color"
    },
    "background": {
      "value": "{color.gray.50}",
      "type": "color"
    }
  },
  "spacing": {
    "sm": { "value": "0.5rem", "type": "spacing" },
    "md": { "value": "1rem", "type": "spacing" },
    "lg": { "value": "2rem", "type": "spacing" }
  }
}
json
{
  "color": {
    "primary": {
      "value": "#3b82f6",
      "type": "color",
      "description": "Primary brand color"
    },
    "background": {
      "value": "{color.gray.50}",
      "type": "color"
    }
  },
  "spacing": {
    "sm": { "value": "0.5rem", "type": "spacing" },
    "md": { "value": "1rem", "type": "spacing" },
    "lg": { "value": "2rem", "type": "spacing" }
  }
}

Component Integration

组件集成

Component-Level Tokens

组件级Token

css
/* Card component with local tokens */
.card {
  /* Component tokens with fallbacks */
  --card-padding: var(--spacing-4, 1rem);
  --card-radius: var(--border-radius-lg, 0.5rem);
  --card-shadow: var(--shadow-md);
  --card-bg: var(--color-surface);
  --card-border: var(--color-border);

  padding: var(--card-padding);
  border-radius: var(--card-radius);
  box-shadow: var(--card-shadow);
  background: var(--card-bg);
  border: 1px solid var(--card-border);
}

/* Variant via token override */
.card--elevated {
  --card-shadow: var(--shadow-lg);
}

.card--outlined {
  --card-shadow: none;
  --card-border: var(--color-border-strong);
}
css
/* Card component with local tokens */
.card {
  /* Component tokens with fallbacks */
  --card-padding: var(--spacing-4, 1rem);
  --card-radius: var(--border-radius-lg, 0.5rem);
  --card-shadow: var(--shadow-md);
  --card-bg: var(--color-surface);
  --card-border: var(--color-border);

  padding: var(--card-padding);
  border-radius: var(--card-radius);
  box-shadow: var(--card-shadow);
  background: var(--card-bg);
  border: 1px solid var(--card-border);
}

/* Variant via token override */
.card--elevated {
  --card-shadow: var(--shadow-lg);
}

.card--outlined {
  --card-shadow: none;
  --card-border: var(--color-border-strong);
}

Responsive Tokens

响应式Token

css
:root {
  --container-padding: var(--spacing-4);
  --heading-size: var(--font-size-xl);
}

@media (min-width: 768px) {
  :root {
    --container-padding: var(--spacing-8);
    --heading-size: var(--font-size-2xl);
  }
}

@media (min-width: 1024px) {
  :root {
    --container-padding: var(--spacing-12);
    --heading-size: var(--font-size-3xl);
  }
}
css
:root {
  --container-padding: var(--spacing-4);
  --heading-size: var(--font-size-xl);
}

@media (min-width: 768px) {
  :root {
    --container-padding: var(--spacing-8);
    --heading-size: var(--font-size-2xl);
  }
}

@media (min-width: 1024px) {
  :root {
    --container-padding: var(--spacing-12);
    --heading-size: var(--font-size-3xl);
  }
}

Tailwind CSS Integration

Tailwind CSS集成

javascript
// tailwind.config.js
module.exports = {
  theme: {
    colors: {
      primary: 'var(--color-primary)',
      'primary-hover': 'var(--color-primary-hover)',
      background: 'var(--color-background)',
      surface: 'var(--color-surface)',
      text: 'var(--color-text)',
      'text-muted': 'var(--color-text-muted)',
    },
    spacing: {
      1: 'var(--spacing-1)',
      2: 'var(--spacing-2)',
      4: 'var(--spacing-4)',
      8: 'var(--spacing-8)',
    },
    borderRadius: {
      sm: 'var(--border-radius-sm)',
      DEFAULT: 'var(--border-radius-md)',
      lg: 'var(--border-radius-lg)',
    },
  },
};
javascript
// tailwind.config.js
module.exports = {
  theme: {
    colors: {
      primary: 'var(--color-primary)',
      'primary-hover': 'var(--color-primary-hover)',
      background: 'var(--color-background)',
      surface: 'var(--color-surface)',
      text: 'var(--color-text)',
      'text-muted': 'var(--color-text-muted)',
    },
    spacing: {
      1: 'var(--spacing-1)',
      2: 'var(--spacing-2)',
      4: 'var(--spacing-4)',
      8: 'var(--spacing-8)',
    },
    borderRadius: {
      sm: 'var(--border-radius-sm)',
      DEFAULT: 'var(--border-radius-md)',
      lg: 'var(--border-radius-lg)',
    },
  },
};

Best Practices

最佳实践

Naming Conventions

命名规范

css
/* Use consistent prefixes */
--color-{category}-{variant}
--spacing-{scale}
--font-{property}-{variant}

/* Examples */
--color-primary-500
--color-text-muted
--spacing-4
--font-size-lg
--font-weight-bold

/* Use semantic, kebab-case names */
--color-brand-primary  /* Semantic + specific */
--spacing-4            /* Scale-based */
--font-weight-bold     /* Consistent kebab-case */
css
/* Use consistent prefixes */
--color-{category}-{variant}
--spacing-{scale}
--font-{property}-{variant}

/* Examples */
--color-primary-500
--color-text-muted
--spacing-4
--font-size-lg
--font-weight-bold

/* Use semantic, kebab-case names */
--color-brand-primary  /* Semantic + specific */
--spacing-4            /* Scale-based */
--font-weight-bold     /* Consistent kebab-case */

Token Scoping

Token作用域

css
/* Global tokens in :root */
:root {
  --color-primary: #3b82f6;
}

/* Component tokens in component scope */
.button {
  --button-bg: var(--color-primary);
}

/* Don't pollute global scope with component tokens */
/* Bad */
:root {
  --button-padding: 1rem;  /* Too specific for global */
}
css
/* Global tokens in :root */
:root {
  --color-primary: #3b82f6;
}

/* Component tokens in component scope */
.button {
  --button-bg: var(--color-primary);
}

/* Don't pollute global scope with component tokens */
/* Bad */
:root {
  --button-padding: 1rem;  /* Too specific for global */
}

Fallback Values

回退值

css
/* Always provide fallbacks for critical styles */
.element {
  color: var(--color-text, #111827);
  padding: var(--spacing-4, 1rem);
}

/* Chain references with fallbacks at the end */
.button {
  background: var(--button-bg, var(--color-primary, #3b82f6));
}
css
/* Always provide fallbacks for critical styles */
.element {
  color: var(--color-text, #111827);
  padding: var(--spacing-4, 1rem);
}

/* Chain references with fallbacks at the end */
.button {
  background: var(--button-bg, var(--color-primary), #3b82f6);
}

Documentation

文档注释

css
/* Document token purpose and usage */

/**
 * Primary brand color
 * Use for: buttons, links, focus rings
 * Contrast: 4.5:1 on white background
 */
--color-primary: #3b82f6;

/**
 * Base spacing unit
 * Use multiples: 2 (8px), 4 (16px), 8 (32px)
 */
--spacing-1: 0.25rem;
css
/* Document token purpose and usage */

/**
 * Primary brand color
 * Use for: buttons, links, focus rings
 * Contrast: 4.5:1 on white background
 */
--color-primary: #3b82f6;

/**
 * Base spacing unit
 * Use multiples: 2 (8px), 4 (16px), 8 (32px)
 */
--spacing-1: 0.25rem;

Common Patterns

常见模式

Color Palette Generation

调色板生成

css
/* Semantic colors referencing primitives */
:root {
  /* Primitive palette */
  --color-blue-50: #eff6ff;
  --color-blue-100: #dbeafe;
  --color-blue-200: #bfdbfe;
  --color-blue-300: #93c5fd;
  --color-blue-400: #60a5fa;
  --color-blue-500: #3b82f6;
  --color-blue-600: #2563eb;
  --color-blue-700: #1d4ed8;
  --color-blue-800: #1e40af;
  --color-blue-900: #1e3a8a;

  /* Semantic mapping */
  --color-primary: var(--color-blue-600);
  --color-primary-light: var(--color-blue-100);
  --color-primary-dark: var(--color-blue-800);
}
css
/* Semantic colors referencing primitives */
:root {
  /* Primitive palette */
  --color-blue-50: #eff6ff;
  --color-blue-100: #dbeafe;
  --color-blue-200: #bfdbfe;
  --color-blue-300: #93c5fd;
  --color-blue-400: #60a5fa;
  --color-blue-500: #3b82f6;
  --color-blue-600: #2563eb;
  --color-blue-700: #1d4ed8;
  --color-blue-800: #1e40af;
  --color-blue-900: #1e3a8a;

  /* Semantic mapping */
  --color-primary: var(--color-blue-600);
  --color-primary-light: var(--color-blue-100);
  --color-primary-dark: var(--color-blue-800);
}

Typography Scale

排版比例

css
:root {
  /* Modular scale (1.25 ratio) */
  --font-size-xs: 0.64rem;   /* 10.24px */
  --font-size-sm: 0.8rem;    /* 12.8px */
  --font-size-base: 1rem;    /* 16px */
  --font-size-lg: 1.25rem;   /* 20px */
  --font-size-xl: 1.563rem;  /* 25px */
  --font-size-2xl: 1.953rem; /* 31.25px */
  --font-size-3xl: 2.441rem; /* 39.06px */

  /* Line heights */
  --line-height-tight: 1.25;
  --line-height-normal: 1.5;
  --line-height-relaxed: 1.75;
}
css
:root {
  /* Modular scale (1.25 ratio) */
  --font-size-xs: 0.64rem;   /* 10.24px */
  --font-size-sm: 0.8rem;    /* 12.8px */
  --font-size-base: 1rem;    /* 16px */
  --font-size-lg: 1.25rem;   /* 20px */
  --font-size-xl: 1.563rem;  /* 25px */
  --font-size-2xl: 1.953rem; /* 31.25px */
  --font-size-3xl: 2.441rem; /* 39.06px */

  /* Line heights */
  --line-height-tight: 1.25;
  --line-height-normal: 1.5;
  --line-height-relaxed: 1.75;
}

Spacing Scale

间距比例

css
:root {
  /* 4px base unit */
  --spacing-0: 0;
  --spacing-1: 0.25rem;  /* 4px */
  --spacing-2: 0.5rem;   /* 8px */
  --spacing-3: 0.75rem;  /* 12px */
  --spacing-4: 1rem;     /* 16px */
  --spacing-5: 1.25rem;  /* 20px */
  --spacing-6: 1.5rem;   /* 24px */
  --spacing-8: 2rem;     /* 32px */
  --spacing-10: 2.5rem;  /* 40px */
  --spacing-12: 3rem;    /* 48px */
  --spacing-16: 4rem;    /* 64px */
}
css
:root {
  /* 4px base unit */
  --spacing-0: 0;
  --spacing-1: 0.25rem;  /* 4px */
  --spacing-2: 0.5rem;   /* 8px */
  --spacing-3: 0.75rem;  /* 12px */
  --spacing-4: 1rem;     /* 16px */
  --spacing-5: 1.25rem;  /* 20px */
  --spacing-6: 1.5rem;   /* 24px */
  --spacing-8: 2rem;     /* 32px */
  --spacing-10: 2.5rem;  /* 40px */
  --spacing-12: 3rem;    /* 48px */
  --spacing-16: 4rem;    /* 64px */
}

Migration Guide

迁移指南

From Hardcoded Values

从硬编码值迁移

css
/* Before */
.button {
  background: #3b82f6;
  padding: 8px 16px;
  border-radius: 4px;
}

/* After */
.button {
  background: var(--color-primary);
  padding: var(--spacing-2) var(--spacing-4);
  border-radius: var(--border-radius-sm);
}
css
/* Before */
.button {
  background: #3b82f6;
  padding: 8px 16px;
  border-radius: 4px;
}

/* After */
.button {
  background: var(--color-primary);
  padding: var(--spacing-2) var(--spacing-4);
  border-radius: var(--border-radius-sm);
}

From Sass Variables

从Sass变量迁移

scss
// Before (Sass)
$primary: #3b82f6;
$spacing-md: 1rem;

.button {
  background: $primary;
  padding: $spacing-md;
}

// After (CSS custom properties)
:root {
  --color-primary: #3b82f6;
  --spacing-4: 1rem;
}

.button {
  background: var(--color-primary);
  padding: var(--spacing-4);
}
scss
// Before (Sass)
$primary: #3b82f6;
$spacing-md: 1rem;

.button {
  background: $primary;
  padding: $spacing-md;
}

// After (CSS custom properties)
:root {
  --color-primary: #3b82f6;
  --spacing-4: 1rem;
}

.button {
  background: var(--color-primary);
  padding: var(--spacing-4);
}

References

参考资料