Темизация дальше тёмной темы
Multi-theme: бренд-темы, density modes, accessibility modes. Стратегия токенов, переключение, что не масштабируется.
Действуй как architect design system. Спроектируй мульти-темизацию для {{product}} c набором: {{themes}}. Цель — не «добавить ещё одну тему», а архитектура, где тема — это axis, а не копипаста.
1. Какие axes темизации существуют
Не всё это «темы» — это разные оси:
- Цветовая схема: light / dark / sepia / amoled.
- Бренд: subbrand-A / subbrand-B / white-label tenant. Меняет accent + radius + шрифт.
- Density: comfortable / compact / spacious. Меняет spacing + size scale + line-height.
- Accessibility: high-contrast / large-text / reduced-motion. Меняет контрасты, размеры, анимации.
- Platform: ios / android / web. Меняет radius, ease, shadow.
Темы можно комбинировать: dark + brand-A + compact + high-contrast. Это 5 axes, не 5 тем.
2. Стратегия токенов (3 слоя)
Layer 1 (raw / primitive): --blue-500, --gray-900, --space-4
Layer 2 (semantic / alias): --color-fg, --color-bg-elevated, --space-card-padding
Layer 3 (component): --button-bg, --button-padding-x
- Тема меняет ТОЛЬКО layer 2. Layer 1 — общая палитра, layer 3 — следует за layer 2.
- Если компонент тянется в layer 1 (
color: var(--blue-500)) — это баг, найди и переведи на семантику.
3. Реализация переключения
Через data-attributes (рекомендую)
<html data-theme="dark" data-brand="acme" data-density="compact" data-contrast="high">
:root { --color-fg: #111; --color-bg: #fff; }
[data-theme="dark"] { --color-fg: #f5f5f5; --color-bg: #0a0a0a; }
[data-brand="acme"] { --color-accent: #5b21b6; }
[data-density="compact"] { --space-card-padding: var(--space-2); }
[data-contrast="high"] { --color-fg: #000; --color-border: #000; }
Почему data-attributes а не класс: ортогональны, любую комбинацию легко выразить, CSP-дружественно, нет conflict resolution.
SSR + no-flash
- Прочти cookie / localStorage до первого рендера (Next.js: middleware или
<script>в<head>синхронно). - Установи
data-themeна<html>до hydration. - Никогда не делай
useEffect(() => setTheme(...))без guard — это даст flash of incorrect theme (FOIT).
Tailwind / CSS-in-JS
- Tailwind v4:
@variant dark (&:where([data-theme=dark], [data-theme=dark] *)). - В styled-components: тема через ThemeProvider, но всё равно используй CSS-переменные внутри — иначе run-time re-render всего дерева.
4. Что плохо масштабируется
- Условия в JS-коде
if (theme === 'dark') ...— превращает компонент в свалку. Условия живут в CSS-переменных, JS не должен знать о теме. - Дублирование стилей
.btn-light/.btn-dark— каждая тема × каждый компонент = N × M, экспоненциальный рост. - Hard-coded hex в компонентах — единственный источник правды это токены layer 2.
- Тема как JS-объект, прокинутый через props — невозможно SSR без mismatch, run-time только.
- Темы зависят от компонента, а не от axes — добавление 6-й темы требует трогать все компоненты.
5. Tooling и QA
- Visual regression по каждой комбинации тем для критичных компонентов (Chromatic / Playwright snapshots).
- Tokens linter: запрет hex / rgb в файлах компонентов; разрешён только в
tokens/. - Storybook: глобальный switcher для всех axes, render каждой story в
light + dark + brand-X. - Contrast CI: автоматическая проверка каждой пары fg/bg в каждой теме.
Формат вывода
- Карта axes темизации для {{product}}.
- Структура папок:
tokens/raw,tokens/semantic,tokens/themes/*.css. - Контракт компонента: какие токены он обязан использовать.
- Switcher: код инициализации без FOIT.
- QA-чеклист: что проверяется в CI.
Anti-patterns (do NOT)
- ❌ Дублирование CSS
.btn-light/.btn-dark— растёт N×M. - ❌
if (theme === 'dark')в JSX компонентов — стили не должны знать про runtime. - ❌ Hard-coded
#5b21b6в компоненте — белый лейбл с другим брендом сломается. - ❌ Тема только через ThemeProvider без CSS-переменных — лишний re-render всего дерева на switch.
- ❌ Темизация через классы вместо data-attributes — комбинации дают conflict resolution ад.
- ❌
useEffect(() => document.body.classList.add('dark'))без SSR-инициализации — FOIT гарантирован. - ❌ Density через scale() / zoom — ломает hit-testing, тени, шрифты. Density меняется spacing-токенами.
- ❌ High-contrast как «жирнее текст» — это не a11y. WCAG AAA = 7:1 contrast, фокус, размер touch-target.
- ❌ Бренд-тема, которая меняет 50 токенов — это не тема, это форк. Тема трогает ≤ 10 семантических.
- ❌ Темы вводятся «потому что красиво», без use-case — каждая тема стоит maintenance, добавляй по запросу.
Аудит доступности по WCAG 2.2 AA
Проверка контраста, клавиатурной навигации, скринридеров, фокус-индикаторов и ARIA.
Аудит brand-consistency
Прогон интерфейса на согласованность: цвета, spacing, typography, voice, иконки. Найти отклонения от системы.
Мастер-аудит сайта: 6 измерений за один проход
Orchestrator-аудит по 6 направлениям: UX, accessibility, performance, SEO, brand consistency, security. Quick scan + deep dive + приоритизированный план + композитная оценка + roadmap.