293 lines
7.4 KiB
Markdown
293 lines
7.4 KiB
Markdown
---
|
||
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;
|
||
}
|
||
```
|