Files
voidea/docs/adr/005-design-tokens.md
T

6.9 KiB

ADR-005: Design Tokens как единый источник стилей

Статус: принято Дата: 2026-05-10


Контекст

Проект VoIdea работает на нескольких платформах:

  • Web (PWA)
  • iOS (Swift/SwiftUI)
  • Android (Kotlin)

Требуется унифицировать стили (цвета, шрифты, отступы) между всеми платформами.

Рассматривались:

  • Ручное копирование — непоследовательно, сложно поддерживать
  • Shared library — требует синхронизации
  • Design Tokens (JSON) — единый источник, генераторы

Решение

Design Tokens в JSON + генераторы для каждой платформы

docs/design-system/tokens.json (источник истины)
         │
         ├── generators/css_generator.py → app/design-tokens/css/theme.css
         ├── generators/swift_generator.py → app/design-tokens/swift/Colors.swift
         └── generators/kotlin_generator.py → app/design-tokens/kotlin/colors.xml

Структура tokens.json

{
  "version": "1.0.0",
  "project": "VoIdea",
  "themes": ["system", "dark", "light"],
  
  "colors": {
    "primary": {
      "500": "#6366F1",
      "600": "#4F46E5",
      "default": "#6366F1",
      "hover": "#4F46E5"
    },
    "background": {
      "system": "auto",
      "dark": "#0F172A",
      "light": "#FFFFFF"
    },
    "semantic": {
      "error": "#EF4444",
      "warning": "#F59E0B",
      "success": "#22C55E",
      "info": "#3B82F6"
    }
  },
  
  "typography": {
    "font_family": {
      "primary": "Inter, system-ui, sans-serif"
    },
    "size": {
      "xs": "0.75rem",
      "sm": "0.875rem",
      "base": "1rem"
    }
  },
  
  "spacing": {
    "xs": "0.25rem",
    "sm": "0.5rem",
    "md": "1rem",
    "lg": "1.5rem"
  },
  
  "border_radius": {
    "sm": "0.25rem",
    "md": "0.5rem",
    "lg": "0.75rem"
  }
}

Генераторы

CSS Generator

# docs/design-system/generators/css_generator.py

def generate(tokens: dict) -> str:
    css = ":root {\n"
    
    for category, values in tokens.items():
        if category == "colors":
            for name, value in flatten(values):
                css += f"  --color-{name}: {value};\n"
        
        elif category == "typography":
            for name, value in flatten(values):
                css += f"  --font-{name}: {value};\n"
        
        elif category == "spacing":
            for name, value in flatten(values):
                css += f"  --spacing-{name}: {value};\n"
    
    css += "}\n"
    return css

Выход: app/design-tokens/css/theme.css

:root {
  --color-primary: #6366F1;
  --color-background-dark: #0F172A;
  --font-family-primary: Inter, system-ui, sans-serif;
  --spacing-md: 1rem;
}

Swift Generator

# docs/design-system/generators/swift_generator.py

def generate(tokens: dict) -> str:
    swift = "import SwiftUI\n\n"
    swift += "enum Colors {\n"
    
    for name, value in flatten(tokens["colors"]):
        snake_to_camel = to_camel_case(name)
        swift += f'    static let {snake_to_camel} = Color(hex: "{value}")\n'
    
    swift += "}\n"
    swift += "enum Typography {\n"
    
    # ... font generation
    
    return swift

Выход: app/design-tokens/swift/Colors.swift

import SwiftUI

enum Colors {
    static let primary = Color(hex: "#6366F1")
    static let backgroundDark = Color(hex: "#0F172A")
}

enum Typography {
    static let fontFamilyPrimary = "Inter"
    static let fontSizeBase: CGFloat = 16
}

Kotlin Generator

# docs/design-system/generators/kotlin_generator.py

def generate(tokens: dict) -> str:
    xml = '<?xml version="1.0" encoding="utf-8"?>\n'
    xml += '<resources>\n'
    
    for name, value in flatten(tokens["colors"]):
        safe_name = name.replace("_", "_")
        xml += f'    <color name="{safe_name}">{value}</color>\n'
    
    xml += '</resources>\n'
    return xml

Выход: app/design-tokens/kotlin/colors.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="primary">#6366F1</color>
    <color name="background_dark">#0F172A</color>
</resources>

Темы

System (Auto)

@media (prefers-color-scheme: dark) {
  :root[data-theme="system"] {
    --color-background: #0F172A;
    --color-text: #F8FAFC;
  }
}

@media (prefers-color-scheme: light) {
  :root[data-theme="system"] {
    --color-background: #FFFFFF;
    --color-text: #0F172A;
  }
}

Dark / Light

[data-theme="dark"] {
  --color-background: #0F172A;
  --color-text: #F8FAFC;
}

[data-theme="light"] {
  --color-background: #FFFFFF;
  --color-text: #0F172A;
}

CI/CD Integration

# .github/workflows/design-system.yml

on:
  push:
    paths:
      - 'docs/design-system/tokens.json'

jobs:
  generate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Generate CSS
        run: python -m generators css
      
      - name: Generate Swift
        run: python -m generators swift
      
      - name: Generate Kotlin
        run: python -m generators kotlin
      
      - name: Commit
        run: |
          git add app/design-tokens/
          git commit -m "style: regenerate design tokens"
          git push

Правила использования

  1. Никогда не редактировать сгенерированные файлы вручную
  2. Все изменения только в tokens.json
  3. После изменения tokens.json → запустить генераторы
  4. DocAgent обновляет документацию

Maintenance

Обновление tokens.json

  1. Открыть docs/design-system/tokens.json
  2. Внести изменения
  3. Запустить python -m generators all
  4. Проверить сгенерированные файлы
  5. Зафиксировать в git

Добавление новых token

{
  "new_token": {
    "system": "auto",
    "dark": "#value",
    "light": "#value"
  }
}

Последствия

Положительные

  • Единый источник истины
  • Согласованность между платформами
  • Легко добавлять темы
  • Автоматическая генерация

Отрицательные

  • Дополнительный слой абстракции
  • Требует генерацию при изменениях
  • Разные форматы вывода

Ответственный

Decision maker: Frontend team Review date: При добавлении новой платформы


Создано: 2026-05-10 Обновляется DocAgent при изменениях