Перейти к содержанию
Innovialabs

Как правильно сделать тёмную тему на сайте

Практика3 мин

Коротко

Опишите цвета через CSS-переменные, по умолчанию берите системную тему из prefers-color-scheme, выбор пользователя храните в localStorage и применяйте его маленьким скриптом в head до отрисовки страницы.

Короткий ответ

Правильная тёмная тема строится на трёх вещах:

  • CSS-переменные (custom properties) для всех цветов — тема меняет значения переменных, а не сотни правил.
  • prefers-color-scheme — по умолчанию сайт следует настройке операционной системы.
  • Ручной переключатель с сохранением выбора и скриптом в <head>, который применяет тему до первой отрисовки. Иначе пользователь увидит вспышку светлой темы.

Шаг 1. Цвета как токены

Не пишите color: #1a1033 в компонентах. Заведите семантические переменные: фон, текст, приглушённый текст, граница, акцент.

:root {
  --bg: #ffffff;
  --text: #1a1033;
  --muted: #5f5873;
  --border: #e4e0ee;
  --accent: #7c3aed;
  color-scheme: light;
}

:root[data-theme="dark"] {
  --bg: #120b24;
  --text: #ede9fe;
  --muted: #a39cb8;
  --border: #2c2343;
  --accent: #a78bfa;
  color-scheme: dark;
}

body {
  background: var(--bg);
  color: var(--text);
}

Свойство color-scheme подсказывает браузеру, что перекрашивать стандартные элементы: скроллбары, поля ввода, чекбоксы. Без него на тёмном фоне останутся светлые нативные контролы.

Шаг 2. Системная тема по умолчанию

Если пользователь ещё ничего не выбирал, уважайте настройку системы. Удобная схема: атрибут data-theme ставится только при ручном выборе, а без него работает медиазапрос.

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #120b24;
    --text: #ede9fe;
    /* остальные переменные тёмной темы */
    color-scheme: dark;
  }
}

Так получаются три состояния: системная, светлая, тёмная. Это честнее, чем двухпозиционный переключатель, который навсегда отключает следование системе.

Шаг 3. Переключатель с сохранением

function setTheme(theme) {
  // theme: "light" | "dark" | "system"
  const root = document.documentElement;
  if (theme === "system") {
    root.removeAttribute("data-theme");
  } else {
    root.setAttribute("data-theme", theme);
  }
  try {
    localStorage.setItem("theme", theme);
  } catch (e) {}
}

Обращение к localStorage заворачивайте в try/catch: в приватном режиме или при заблокированных cookies оно может выбросить ошибку.

Шаг 4. Убираем вспышку при загрузке

Если тема применяется после загрузки основного JavaScript, страница сначала рисуется светлой, а потом резко темнеет. Решение — крошечный блокирующий скрипт в <head> до стилей и контента:

<script>
  try {
    var t = localStorage.getItem("theme");
    if (t === "light" || t === "dark") {
      document.documentElement.setAttribute("data-theme", t);
    }
  } catch (e) {}
</script>

Он выполняется до первой отрисовки, поэтому вспышки нет. В React и Next.js учитывайте, что сервер не знает выбор пользователя: атрибут на <html> меняется до гидрации, и фреймворк может предупредить о несовпадении. Обычно на <html> для этого ставят suppressHydrationWarning или используют готовую библиотеку для тем.

Что часто делают неправильно

  • Просто инвертируют цвета через filter: invert(). Ломаются фото, логотипы и брендовые цвета.
  • Чистый чёрный фон и чисто белый текст. Слишком высокий контраст утомляет глаза; лучше глубокий тёмный оттенок и слегка приглушённый светлый текст.
  • Забывают про тени. На тёмном фоне тени почти не видны — глубину лучше передавать более светлыми поверхностями.
  • Не проверяют контраст. Приглушённый текст и акцентные кнопки часто проваливают требования WCAG в тёмной теме.
  • Забывают картинки и графики. Иллюстрациям с белым фоном нужна отдельная версия или подложка, графикам — свои цвета.

Чек-лист перед релизом

  • Все цвета в компонентах идут через переменные.
  • Есть color-scheme для обеих тем.
  • Без ручного выбора сайт следует системе.
  • Выбор сохраняется и применяется без вспышки.
  • Контраст проверен для текста, ссылок, кнопок и состояний фокуса.
  • Логотип, иконки и изображения читаются на обоих фонах.

Частые вопросы

Нет. Это улучшение удобства, а не требование. Но если вы её делаете, она должна быть полноценной: недоделанная тёмная тема с нечитаемыми блоками хуже, чем её отсутствие.

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.