--- name: system/design-tokens title: Design Tokens System description: Complete CSS custom properties system for consistent spacing, typography, color, and motion tokens. tags: [system, design-tokens, css-variables, architecture, consistency] source: stripe.com, linear.app, raycast.com, design-systems.dev --- # Design Tokens System ## Что это Единая система CSS-переменных (custom properties), которая обеспечивает консистентность дизайна. Все размеры, цвета, шрифты, анимации управляются из одного места. ## Архитектура ### File Structure ``` css/ ├── tokens/ │ ├── color.css # Цветовая палитра │ ├── spacing.css # Масштаб отступов │ ├── typography.css # Масштаб шрифтов │ ├── motion.css # Параметры анимаций │ └── layout.css # Breakpoints, container sizes ├── components/ # Компоненты используют tokens └── pages/ # Страницы используют components ``` ### CSS Custom Properties ```css /* tokens/color.css */ :root { /* Brand colors */ --color-brand-50: #f0f5ff; --color-brand-100: #e0eaff; --color-brand-200: #b9d0ff; --color-brand-400: #4f8fff; --color-brand-500: #2563eb; --color-brand-600: #1d4ed8; --color-brand-700: #1e40af; /* Semantic colors */ --color-bg: #0a0a0a; --color-surface: #111111; --color-surface-hover: #1a1a1a; --color-border: rgba(255,255,255,0.06); --color-border-hover: rgba(255,255,255,0.12); /* Text colors */ --color-text-primary: #f5f5f5; --color-text-secondary: #a0a0a0; --color-text-muted: #666666; /* Accent colors */ --color-accent: #6366f1; --color-accent-hover: #818cf8; --color-success: #10b981; --color-warning: #f59e0b; --color-danger: #ef4444; } ``` ```css /* tokens/spacing.css */ :root { --space-1: 4px; --space-2: 8px; --space-3: 12px; --space-4: 16px; --space-5: 20px; --space-6: 24px; --space-8: 32px; --space-10: 40px; --space-12: 48px; --space-16: 64px; --space-20: 80px; --space-24: 96px; --space-32: 128px; } ``` ```css /* tokens/typography.css */ :root { --font-primary: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; --font-mono: 'JetBrains Mono', 'Fira Code', monospace; /* Scale ratio: 1.25 (Major Third) */ --text-xs: 0.75rem; --text-sm: 0.875rem; --text-base: 1rem; --text-lg: 1.125rem; --text-xl: 1.25rem; --text-2xl: 1.5rem; --text-3xl: 1.875rem; --text-4xl: 2.25rem; --text-5xl: 3rem; --text-6xl: 3.75rem; --text-7xl: 4.5rem; /* Line heights */ --leading-tight: 1.1; --leading-snug: 1.3; --leading-normal: 1.6; --leading-relaxed: 1.75; } ``` ```css /* tokens/motion.css */ :root { --ease-out: cubic-bezier(0.4, 0, 0.2, 1); --ease-in: cubic-bezier(0.4, 0, 1, 1); --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); --ease-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55); --duration-fast: 150ms; --duration-normal: 300ms; --duration-slow: 500ms; --duration-xslow: 800ms; } ``` ```css /* tokens/layout.css */ :root { --container-sm: 640px; --container-md: 768px; --container-lg: 1024px; --container-xl: 1200px; --container-2xl: 1400px; --breakpoint-sm: 640px; --breakpoint-md: 768px; --breakpoint-lg: 1024px; --breakpoint-xl: 1280px; } ``` ## CSS Reset с токенами ```css /* css/reset.css */ *, *::before, *::after { box-sizing: border-box; } html { font-size: 100%; /* 16px */ scroll-behavior: smooth; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } body { font-family: var(--font-primary); background: var(--color-bg); color: var(--color-text-primary); line-height: var(--leading-normal); overflow-x: hidden; } /* Typography with tokens */ h1 { font-size: var(--text-5xl); line-height: var(--leading-tight); font-weight: 800; } h2 { font-size: var(--text-4xl); line-height: var(--leading-tight); font-weight: 700; } h3 { font-size: var(--text-2xl); line-height: var(--leading-snug); font-weight: 600; } /* Spacing with tokens */ .p-4 { padding: var(--space-4); } .p-6 { padding: var(--space-6); } .p-8 { padding: var(--space-8); } .gap-4 { gap: var(--space-4); } .gap-6 { gap: var(--space-6); } ``` ## Component Tokens ```css /* tokens/components.css */ :root { /* Buttons */ --btn-radius: 10px; --btn-padding-y: var(--space-3); --btn-padding-x: var(--space-6); --btn-font-weight: 600; --btn-font-size: var(--text-base); /* Cards */ --card-radius: 16px; --card-padding: var(--space-8); --card-border: 1px solid var(--color-border); --card-bg: var(--color-surface); --card-hover-shadow: 0 4px 20px rgba(0,0,0,0.3); /* Inputs */ --input-radius: 8px; --input-padding: var(--space-3) var(--space-4); --input-border: 1px solid var(--color-border); --input-focus-border: 1px solid var(--color-brand-500); } ``` ## Theming with Tokens ### Light Theme ```css :root { --color-bg: #ffffff; --color-surface: #f5f5f5; --color-text-primary: #0a0a0a; --color-text-secondary: #555555; --color-border: rgba(0,0,0,0.08); } [data-theme="light"] { --color-bg: #ffffff; --color-surface: #f5f5f5; --color-text-primary: #0a0a0a; } [data-theme="dark"] { --color-bg: #0a0a0a; --color-surface: #111111; --color-text-primary: #f5f5f5; } ``` ### Dark Theme (default) ```css :root { --color-bg: #0a0a0a; --color-surface: #111111; --color-text-primary: #f5f5f5; } ``` ## Utility Classes ```css /* tokens/utilities.css */ .text-primary { color: var(--color-text-primary); } .text-secondary { color: var(--color-text-secondary); } .text-muted { color: var(--color-text-muted); } .bg-surface { background: var(--color-surface); } .border-default { border: var(--color-border); } .border-hover:hover { border-color: var(--color-border-hover); } .rounded-sm { border-radius: 4px; } .rounded { border-radius: 8px; } .rounded-md { border-radius: 12px; } .rounded-lg { border-radius: 16px; } .rounded-xl { border-radius: 24px; } .rounded-full { border-radius: 9999px; } /* Container */ .container { max-width: var(--container-xl); margin: 0 auto; padding: 0 var(--space-6); } ``` ## Anti-patterns | ❌ Плохо | ✅ Хорошо | |----------|-----------| | `padding: 23px` | `padding: var(--space-6)` (24px) | | `color: #f0f0f0` | `color: var(--color-text-primary)` | | `font-size: 32px` | `font-size: var(--text-4xl)` | | `border-radius: 15px` | `border-radius: var(--card-radius)` | | `transition: 0.2s` | `transition: var(--duration-normal) var(--ease-out)` | ## Best Practices 1. **Один источник истины** — все токены в `tokens/` 2. **Называйте по значению** — `--spacing-md`, `--color-primary`, не `--padding-1`, `--brand-1` 3. **Без hardcoded значений в компонентах** — компоненты только токены 4. **Планируйте на 20% вперёд** — если сейчас нужно 5 размеров spacing, добавьте ещё 2 5. **Dokumentiruyte** — каждый токен должен быть описан ```css /* ✅ Хороший токен */ :root { /** Отступ между карточками в сетке */ --card-gap: 24px; /** Максимальная ширина контента */ --max-width: 1200px; } ```