~/wiki / dizayn-sistema-i-komponenty / tyomnaya-tema-arkhitektura-cherez-peremennye

Тёмная тема за один день — если ты правильно настроил переменные заранее

Основной чат

Чат для вайбкодеров: новости, гайды, поиск исполнителей, маркетплейс и разбор реальных кейсов.

$ cd раздел/ $ join vibe dev
Тёмная тема за один день — если ты правильно настроил переменные заранее - обложка

Есть два способа добавить тёмную тему. Первый: открываешь каждый компонент, меняешь цвета вручную, смотришь как половина рассыпается, переделываешь три дня, всё равно что-то пропускаешь. Второй: создаёшь Dark Mode в Figma Variables, задаёшь тёмные значения, переключаешь — всё обновляется за секунду. Один день вместо трёх.

Разница — в одном решении принятом в начале: использовать семантические токены или нет.


Тёмная тема это не инверсия

Самая распространённая ошибка: сделать тёмную тему как механически инвертированные цвета светлой. Результат выглядит странно — и это не случайность.

Оптическая вибрация. Насыщенные цвета на тёмном фоне вибрируют — это физиологический эффект. #E94560 на белом фоне выглядит энергично. Тот же цвет на #1A1A2E — агрессивно и дискомфортно. Акценты в тёмной теме нужно делать менее насыщенными или чуть светлее.

Тени исчезают. box-shadow: 0 4px 12px rgba(0,0,0,0.1) — тёмная тень на светлом фоне создаёт глубину. На тёмном фоне она почти невидима. В тёмной теме elevation показывается через светлость поверхности: то что «выше» — светлее.

Contrast ratio меняется. Цвет который проходит WCAG AA 4.5:1 в светлой теме может не проходить в тёмной. Особенно серые оттенки. После добавления тёмной темы — отдельная проверка каждого текстового элемента.


Архитектура Variables: Light и Dark Mode

Figma Variables, Collections, два Mode: Light и Dark. Каждый компонент использует семантический токен — не конкретный цвет. При переключении Mode меняются значения под теми же именами.

plaintext
Коллекция: Color System
Modes: Light | Dark

color/background/page:
  Light → #FFFFFF
  Dark  → #0F0F1A     ← темнее чем surface

color/background/surface:
  Light → #F5F5F0
  Dark  → #1A1A2E

color/background/elevated:
  Light → #FFFFFF (+ тень)
  Dark  → #252540     ← светлее surface, elevation через яркость

color/text/primary:
  Light → #1A1A2E
  Dark  → #F5F5F0

color/text/secondary:
  Light → #4A4A6A
  Dark  → #9090B0

color/text/disabled:
  Light → #AAAACC
  Dark  → #505070

color/brand/accent:
  Light → #E94560
  Dark  → #D4304A     ← чуть темнее, убирает вибрацию

color/border/default:
  Light → #E0E0F0
  Dark  → rgba(255,255,255,0.1)

color/status/error:
  Light → #E94560
  Dark  → #FF6080     ← чуть светлее для видимости на тёмном

Elevation через светлость, не тени

В тёмной теме карточка должна «подниматься» над фоном страницы — но тени не работают. Решение: каждый уровень elevation немного светлее.

css
:root[data-theme="dark"] {
  --color-background-page:     #0F0F1A;   /* самый тёмный */
  --color-background-surface:  #1A1A2E;
  --color-background-elevated: #252540;   /* карточки, модали */
  --color-background-overlay:  #303060;   /* тултипы, dropdown */
}

.card {
  background: var(--color-background-elevated);
  border: 1px solid rgba(255, 255, 255, 0.08);
  /* box-shadow убирается в тёмной теме */
}

Компоненты которые всегда ломаются

Инпуты. Placeholder теряет контраст на тёмном фоне — часто не проходит 3:1. Border в idle-состоянии почти невидим. Проверяй оба.

PNG с прозрачностью. Логотипы с белыми деталями на тёмном фоне выглядят странно. Варианты: SVG который стилизуется через CSS (fill: currentColor), или <picture> с отдельной версией для тёмной темы.

Графики. Цвета Chart.js/Recharts обычно захардкожены в конфиге. Нужен хук:

javascript
function useChartColors() {
  const style = getComputedStyle(document.documentElement)
  return {
    primary:   style.getPropertyValue('--color-chart-primary').trim(),
    secondary: style.getPropertyValue('--color-chart-secondary').trim(),
    grid:      style.getPropertyValue('--color-chart-grid').trim(),
  }
}

Иконки с захардкоженным fill. fill: #1A1A2E невидима на тёмном фоне. fill: currentColor работает автоматически. Проведи отдельный аудит иконок.

Модальный оверлей. rgba(0,0,0,0.5) на тёмном фоне почти не даёт разделения с контентом. Нужно rgba(0,0,0,0.7) или backdrop-filter: blur(4px).


CSS: переключение без вспышки

css
:root {
  --color-background-page:    #FFFFFF;
  --color-background-surface: #F5F5F0;
  --color-text-primary:       #1A1A2E;
  --color-brand-accent:       #E94560;
  --color-border-default:     #E0E0F0;
}

/* Системный режим (без ручного переключения) */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --color-background-page:    #0F0F1A;
    --color-background-surface: #1A1A2E;
    --color-text-primary:       #F5F5F0;
    --color-brand-accent:       #D4304A;
    --color-border-default:     rgba(255,255,255,0.1);
  }
}

/* Ручное переключение */
[data-theme="dark"] {
  --color-background-page:    #0F0F1A;
  --color-background-surface: #1A1A2E;
  --color-text-primary:       #F5F5F0;
  --color-brand-accent:       #D4304A;
  --color-border-default:     rgba(255,255,255,0.1);
}

/* Плавный переход — ТОЛЬКО цвета, не всё */
*, *::before, *::after {
  transition: background-color 0.2s ease, color 0.15s ease,
              border-color 0.15s ease;
}

transition: all сломает существующие анимации. Только color-свойства.


Когда начинать: до или после запуска

Если продукт ещё не запущен — строй с тёмной темой сразу. При правильной архитектуре токенов это почти бесплатно: добавляешь Dark Mode в Variables и задаёшь значения.

Если продукт уже запущен без токенов — сначала переводи на Variables, потом добавляй тёмную тему. Пытаться добавить тёмную тему без предварительного перевода на переменные — это переделка каждого компонента дважды. Дороже в два раза.

Тёмная тема — хорошая причина навести порядок в системе: чтобы она заработала, всё должно быть на семантических токенах. Захардкоженные значения вылезут сразу. Это не проблема — это шанс зафиксировать долг и разобраться с ним.


Промпт для Codex / Claude Code

markdown
Добавь поддержку тёмной темы в проект.

Токены тёмной темы:
- background/page: #0F0F1A
- background/surface: #1A1A2E
- background/elevated: #252540
- text/primary: #F5F5F0
- text/secondary: #9090B0
- brand/accent: #D4304A (НЕ #E94560 — вибрирует на тёмном)
- border/default: rgba(255,255,255,0.1)
- status/error: #FF6080

Требования:
1. Все значения через CSS Custom Properties (нет захардкоженных hex)
2. Поддержка prefers-color-scheme И ручного [data-theme="dark"]
3. Кнопка переключения в хедере, сохранение выбора в localStorage
4. В тёмной теме: elevation через светлость поверхностей, не box-shadow
5. transition только для color-свойств (не transition: all)

После добавления:
- Проверь contrast ratio text/primary на каждом фоне (цель ≥ 4.5:1)
- Замени box-shadow карточек на border: 1px solid rgba(255,255,255,0.08)
- Найди иконки с захардкоженным fill и замени на currentColor
- Проверь placeholder в инпутах (contrast ≥ 3:1)

Как тёмная тема ломает вещи которые казались надёжными

Опыт команд которые добавляли тёмную тему без подготовки: сначала кажется что всё работает. Потом начинают появляться баги.

Самый неожиданный: кастомные иконки нарисованные в Figma и экспортированные как SVG с захардкоженным fill="#1A1A2E". В светлой теме — тёмные иконки на светлом фоне. В тёмной теме — тёмные иконки на тёмном фоне. Невидимы. И никто не думал об этом при создании.

Второй неожиданный: emails и PDF которые генерируются из продукта. Они используют светлые цвета потому что печатаются или смотрятся в email-клиентах которые не поддерживают тёмный режим. Токены здесь не помогают — нужны отдельные правила для этих контекстов.

Третий: скриншоты и превью которые генерирует продукт (превью ссылок в чатах, thumbnails). Они обычно рендерятся в headless браузере который игнорирует пользовательские предпочтения. Нужно явно задавать светлую тему для этих сценариев.

Всё это не делает тёмную тему неосуществимой. Просто нужно проверить все контексты в которых используется продукт — не только основное приложение.


Тёмная тема как момент для проверки accessibility

По статистике WCAG проверок, большинство accessibility-проблем с контрастом обнаруживается именно при добавлении тёмной темы. Потому что в светлой теме команда привыкла к конкретным цветам и не проверяет их — они «и так работают».

При добавлении тёмной темы нужно проверить каждую комбинацию фон/текст. Хороший инструмент для этого: плагин Stark в Figma — он проверяет контраст прямо в Figma-файле при переключении на Dark Mode. Chrome DevTools также показывает contrast ratio при инспекции текстовых элементов.

Минимальный набор что проверять: основной текст на каждом фоне (page, surface, elevated), вторичный текст, placeholder в инпутах, текст в кнопках всех вариантов, текст статусных сообщений (error, success, warning).


Частые вопросы про тёмную тему

«Нужно ли поддерживать тёмную тему если у нас мало пользователей на мобайле?» Тёмная тема нужна не только мобайлу. Десктоп-пользователи на macOS и Windows также используют системный тёмный режим. По данным 2025–2026, около 55% пользователей десктопа включили тёмную тему в системных настройках.

«Нужно ли тестировать каждый экран в тёмной теме?» Да. Но не каждый компонент — они тестируются в Storybook. Каждый экран нужно просмотреть визуально при переключении в Figma Dark Mode, и затем верифицировать в браузере с [data-theme="dark"].

«Что делать с пользователями у которых старый браузер без поддержки prefers-color-scheme Для них работает светлая тема по умолчанию. Тёмная тема через prefers-color-scheme — прогрессивное улучшение. Ручное переключение через [data-theme] работает везде где работает JavaScript — то есть в 99.5% браузеров.

«Как тёмная тема влияет на производительность?» CSS Custom Properties переключаются мгновенно — один атрибут на :root. Никакой дополнительной загрузки, никакого мерцания (если правильно настроен transition). Производительность идентична светлой теме.

plaintext
☐ Все компоненты на Variables (нет захардкоженного hex)
☐ Dark Mode создан в Figma Variables
☐ Акценты в тёмной теме: менее насыщены
☐ Elevation: карточки светлее фона, без box-shadow
☐ Контраст текста ≥ 4.5:1 в тёмной теме
☐ Placeholder в инпутах ≥ 3:1
☐ PNG с прозрачностью проверены
☐ Иконки: fill: currentColor вместо захардкоженного цвета
☐ Переключение без вспышки (transition только color-свойства)
$ cd ../ ← назад к Дизайн-система и компоненты