N1Nex1on
N1Nex1on

Фишки оформления

Тёмная тема без мигания: переключатель, который запоминает выбор

25.10.2026 · NexVoid

4 мин чтения

Кнопка тёмной темы, которая не вспыхивает белым при перезагрузке: короткий скрипт в head, light-dark() и color-scheme. Код целиком, снимки на телефоне, планшете и компьютере и проверка, что вспышки нет.

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

Что получится #

  • Тема по умолчанию совпадает с темой системы (светлой или тёмной) и работает даже при выключенном JavaScript.
  • Кнопка переключает тему, а выбор запоминается в браузере.
  • Сохранённый выбор применяется до первой отрисовки: при перезагрузке нет белой вспышки.
Тёмная тема на телефоне, ширина 390: шапка с кнопкой «Тёмная» и карточка
Тёмная тема на телефоне, ширина 390: шапка с кнопкой «Тёмная» и карточка
Тёмная тема на планшете, ширина 768
Тёмная тема на планшете, ширина 768
Тёмная тема на компьютере, ширина 1280: карточка по центру, лишнего нет
Тёмная тема на компьютере, ширина 1280: карточка по центру, лишнего нет

Почему страница мигает #

Браузер рисует страницу по мере чтения HTML. Если выбор пользователя применяет скрипт в конце страницы или после загрузки, браузер успевает нарисовать светлый вариант, а потом перекрашивает его. Нужно, чтобы выбор был известен раньше, чем появится первый пиксель, то есть скрипт должен стоять в самом начале страницы, в head.

Шаг 1. Объявите, что сайт умеет обе темы #

Положите в head строку meta и сразу за ней крошечный скрипт. Он читает сохранённый выбор и, если он есть, записывает его в атрибут data-theme корневого элемента:
headHTML
<meta name="color-scheme" content="light dark">
<script>
(function () {
  var saved = null;
  try { saved = localStorage.getItem('theme'); } catch (e) {}
  if (saved === 'light' || saved === 'dark') {
    document.documentElement.dataset.theme = saved;
  }
})();
</script>
Скрипт короткий и не ждёт загрузки страницы. Блок try нужен потому, что в приватном режиме или при запрещённом хранилище браузер бросает ошибку, и без него вся страница осталась бы без темы.

Шаг 2. Опишите цвета один раз #

Цвета каждой темы задаются парой значений в функции light-dark(): первое для светлой темы, второе для тёмной. Выбирает нужное свойство color-scheme:
style.cssCSS
:root {
  color-scheme: light dark;
  --bg: light-dark(#ffffff, #14161a);
  --text: light-dark(#1b1f24, #e6e8eb);
  --card: light-dark(#f3f5f8, #1e2228);
  --line: light-dark(#d8dee6, #2c333c);
  --accent: light-dark(#1a56db, #7aa7ff);
}
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"] { color-scheme: dark; }
body { background: var(--bg); color: var(--text); }
Пока data-theme нет, работает color-scheme: light dark: страница берёт тему системы без единой строки JavaScript. Когда скрипт записал выбор пользователя, две последние строки принудительно включают нужную тему.

Шаг 3. Добавьте кнопку #

bodyHTML
<button id="theme-toggle" type="button" aria-pressed="false"></button>
Кнопка из двух состояний проще трёх («светлая, тёмная, как в системе»): понятно, что нажато, и не надо объяснять третий вариант. Пока человек не нажал, сайт и так следует за системой.
theme.jsJavaScript
(function () {
  var root = document.documentElement;
  var button = document.getElementById('theme-toggle');
  var names = { dark: 'Тёмная', light: 'Светлая' };

  function current() {
    return root.dataset.theme ||
      (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
  }
  function show() {
    var now = current();
    button.textContent = names[now];
    button.setAttribute('aria-pressed', now === 'dark');
  }

  button.addEventListener('click', function () {
    var next = current() === 'dark' ? 'light' : 'dark';
    root.dataset.theme = next;
    try { localStorage.setItem('theme', next); } catch (e) {}
    show();
  });
  show();
})();
Этот скрипт можно подключить в конце страницы: ему не нужно успевать до отрисовки, он лишь подписывает кнопку и сохраняет выбор.

Проверьте сами #

Мы проверили рецепт в браузере на странице, где перед концом стоит медленный скрипт (две секунды), а в хранилище записан выбор «тёмная» при светлой теме системы. Через 0,8 секунды после начала загрузки: со скриптом в head страница уже тёмная, со скриптом в конце — белая, и так остаётся, пока страница не дочитана. Вот что видит человек в этот момент:
Скрипт в конце страницы: выбор «тёмная» сохранён, а страница светлая, это вспышка
Скрипт в конце страницы: выбор «тёмная» сохранён, а страница светлая, это вспышка
Скрипт в head: тот же момент загрузки, страница сразу тёмная
Скрипт в head: тот же момент загрузки, страница сразу тёмная

Остальное тоже проверено: без JavaScript страница берёт тему системы, после нажатия кнопки и перезагрузки выбор остаётся, на 390, 768 и 1280 точек нет бокового скролла.
Повторить можно так:

  1. Откройте страницу, нажмите кнопку и выберите тёмную тему.
  2. Включите в инструментах разработчика замедление сети и перезагрузите страницу: фон остаётся тёмным с первого кадра.
  3. Выключите JavaScript и очистите хранилище: страница снова следует теме системы.

Типичные ошибки #

  • Скрипт в конце страницы. Вспышка возвращается: выбор известен слишком поздно.
  • Нет color-scheme на :root. Функция light-dark() без него не знает, какое значение взять.
  • Нет try вокруг localStorage. В приватном режиме у части браузеров страница ломается.
  • Тема только в JavaScript. Без скрипта посетитель остаётся без тёмной темы даже при включённой в системе. В рецепте это решено через color-scheme.
  • Не проверен контраст. Текст и фон должны давать не меньше 4,5 к 1, это правило доступности WCAG AA. Проверьте обе темы, а не одну.

← К ленте новостей

Поделиться: Telegram WhatsApp

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

Обсуждение

Комментарии

Обсуждение ещё не началось

Пока никто не высказался. Будьте первым.

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