Переключатель тёмной темы позволяет пользователям переопределять цветовую схему системы для одного сайта.
Переопределение применяется путем обновления <meta name="color-scheme">: light dark означает "следовать системным настройкам", light или dark закрепляет явную схему.
Цвета (light-dark()), системные цвета и нативный интерфейс зависят от свойства CSS color-scheme, поэтому переключатель синхронизирует это свойство с <meta> (см. раздел Отражение переопределения в CSS).
Постоянный переключатель, элемент управления настройками или вообще без переключателя?
Большинству сайтов не нужен постоянный переключатель цветовой схемы в интерфейсе (заголовке, навигации, нижнем колонтитуле и т. д.). По умолчанию используйте системные настройки. Если нужно переопределить их вручную, поместите элемент управления на отдельную страницу, панель или оверлей.
Закрепление схемы в ОС – это допустимое, но редкое намерение, и редкие намерения должны быть скрыты за постепенным раскрытием, а не в элементе управления, который виден постоянно.
Постоянные переключатели в основном встречаются на сайтах для разработчиков или смежных специалистов (например, дизайнеров), где переключение схем является гораздо более частой задачей, чем для обычного веб-пользователя. НЕ добавляйте его только потому, что это распространенный шаблон на сайтах разработчиков: интерес разработчика к этому элементу управления не является репрезентативным для типичных пользователей.
Реализация
Сначала выполните все действия, описанные в разделе Тёмная тема, чтобы поддерживать обе схемы, по умолчанию используя системные настройки.
Модель данных
Модель данных для переключателя тёмной темы обычно включает три состояния:
- "Системная" (значение не сохраняется) – интерфейс соответствует цветовой схеме системы. В идеале это должно быть значение по умолчанию.
- Светлая (сохраняется как
"light"). Используется светлая тема независимо от цветовой схемы системы. - Тёмная (сохраняется как
"dark") – интерфейс использует тёмную тему независимо от цветовой схемы системы.
Обычно это состояние хранится в localStorage и отражается в элементе <meta name="color-scheme"> в заголовке документа.
Отразите переопределение в CSS
<meta> – это лишь подсказка для представления. Любое объявление color-scheme в авторском CSS переопределяет его.
Поскольку тёмная тема задает color-scheme: light dark для :root, обновление только <meta> не даст результата, так как CSS имеет приоритет.
Сопоставьте <meta> с ресурсом, чтобы каскад следовал за ним:
:root {
color-scheme: light dark;
&:has(> head > meta[name="color-scheme"][content="dark"]) {
color-scheme: dark;
}
&:has(> head > meta[name="color-scheme"][content="light"]) {
color-scheme: light;
}
}
НЕ задавайте в коде color-scheme: light или color-scheme: dark в качестве корневого значения по умолчанию. Базовым объявлением ДОЛЖНО оставаться light dark.
CSS не должен зависеть от JS. Если JS не запускается, <meta> остается light dark, а сайт следует системным настройкам. Ничего не ломается.
Сохранение состояния и предотвращение FOUC
Сохраните переопределение в localStorage; удалите запись при возврате к системным настройкам по умолчанию.
Чтобы избежать мерцания неправильной схемы, примените сохраненное значение во встроенном скрипте (НЕ type=module, НЕ defer), размещенном сразу после элемента <meta>:
<meta name="color-scheme" content="light dark">
<script>
document.querySelector('meta[name="color-scheme"]').content = localStorage.getItem("color-scheme") ?? "light dark";
</script>
- Этот встроенный скрипт существует только для предотвращения FOUC. Сделайте его как можно меньше. Дополнительный JS-код для переключения цветовой схемы можно загрузить позже.
- В редких случаях доступ к
localStorageможет вызвать ошибку (например, если данные сайта заблокированы), но поскольку этот скрипт больше ничего не делает, единственным побочным эффектом будет ошибка в консоли. Если это важно, заключите в тегtry .. catch. Убедитесь, что тема по-прежнему применяется к странице, даже если ее нельзя сохранить. - Переопределение может измениться на другой вкладке. Чтобы синхронизировать данные, обработайте событие
storageобъектаwindow. - НЕ используйте
matchMedia(), чтобы удалить сохраненное значение, когда системные настройки меняются в соответствии с ним. У многих пользователей ОС автоматически переключает схемы в зависимости от времени суток. Если удалять сохраненное значение при совпадении схем, то закрепить одну из них будет невозможно.
Ветвление для HTML и нецветовых значений
Цвета не требуют дополнительной настройки: после сопоставления <meta> с color-scheme (см. выше) light-dark() следует за переопределением.
В этом разделе рассказывается, как адаптировать другие значения (например, font-weight, источники медиаконтента и т. д.).
Если страница просто следует системным настройкам, (prefers-color-scheme: dark) можно использовать для ветвления, в том числе в источниках <picture> или <video>.
Переопределение цветовой схемы добавляет дополнительную сложность: системные настройки больше не являются источником истины, а для API, которые принимают только запросы медиа, нет прямой альтернативы.
НЕ полагайтесь исключительно на классы, применяемые с помощью JS, например .dark, для ветвления, поскольку они будут неверными, если JS не загрузится, а в ОС по умолчанию используется темная тема.
Вы можете объединить запрос медиа с селектором :root:has(> head > meta[name="color-scheme"][content="dark"]) (или light), чтобы создать ветвь.
Подробнее о том, как это сделать, читайте в разделе Комбинации селекторов и @-правил.
Собственные параметры элемента управления также являются ветвями без цвета: включите оба состояния в разметку (значок и визуально скрытый текст действия, например "Переключиться на тёмную тему") и скройте неактивное с помощью этих селекторов.
display: none также удаляет избыточный вариант из вспомогательных технологий, поэтому элементу управления не нужен JS, чтобы оставаться актуальным при изменении системных настроек.
Для медиаконтента в HTML сейчас есть только один способ – добавить обе версии как отдельные элементы и настроить их видимость.
Скрыть элементы <source> с помощью display: none не получится.
Пользовательский интерфейс
Два или три штата?
В любом случае в интерфейсе ДОЛЖНА быть возможность вернуться к системной цветовой схеме или установить ее.
Если места мало, лучше использовать переключатели с двумя состояниями. Например, это могут быть постоянные переключатели в шапке, меню навигации, подвале и других элементах сайта, где мало места и изменение настроек обычно не связано с целью пользователя.
Если же элемент управления цветовой схемой находится в отдельном разделе настроек (на странице, панели или в оверлее), то в нем МОГУТ быть представлены все три состояния ("Светлая", "Темная", "Системная"). В этом случае пользователь уже принимает осознанные решения о будущем поведении, и есть возможность объяснить варианты.
Три явных состояния также оправданы, если на сайте реализованы контекстно-зависимые схемы (например, более тусклый свет, когда в ОС темно), поскольку тогда «Светлая тема» и «Системный (в настоящее время светлый)» действительно различаются.
Двухпозиционные переключатели
Когда используется такой элемент управления, он, как правило, предназначен для временной регулировки комфорта ("сейчас слишком ярко"), а не для обеспечения сохранения текущего состояния в долгосрочной перспективе. Поэтому для двухпозиционных переключателей достаточно отображать две из трех настроек: системную (чтобы можно было отменить настройку) и текущую противоположную системной, сохраненную как ее текущее литеральное значение.
Должно быть только два состояния:
- Системные настройки по умолчанию – значение не сохраняется,
<meta>контентlight dark. Отображается как текущее разрешенное значение (например, значок солнца при свете). - Переопределение – сохраняется в виде
lightилиdark.
По сути, это трехпозиционный переключатель (light dark, light, dark), в котором явное состояние, соответствующее текущим системным настройкам, недоступно.
Когда пользователь переключает:
- Целевая схема – противоположная текущей отображаемой схеме (сохраненное значение, если есть, или системные настройки). Намерение пользователя – выбрать противоположное тому, что он видит прямо сейчас, а не противоположное системным настройкам по умолчанию.
- Если целевой язык отличается от текущих системных настроек, сохраните его буквально.
- Если целевое значение совпадает с текущими системными настройками, пользователь отменяет корректировку: удалите сохраненное значение. НЕ сохраняйте его, иначе схема будет незаметно закреплена и не будет реагировать на будущие изменения системы.
- Задайте для контента
<meta>сохраненное значение илиlight dark, если его нет.
ВАЖНО! Расхождение должно проверяться только во время сохранения, а не задним числом. Сохраненное значение, которое позже было изменено в системных настройках, ДОЛЖНО быть сохранено. У многих пользователей ОС автоматически переключает схемы в зависимости от времени суток. Если удалять сохраненное значение при совпадении схем, то закрепить одну из них будет невозможно.
Пример сценария, когда в ОС установлена светлая тема:
- Пользователь переключает. Тёмная тема отличается от системных настроек, поэтому сохраняется значение
dark, и сайт становится тёмным. - В настройках ОС будет выбрана тёмная тема. Сайт остается темным (сохраненное значение теперь совпадает с системными настройками, но сохраняется).
- В настройках ОС снова будет выбрана светлая тема. Сайт останется темным.
- Пользователь переключает. Целевая тема (светлая) совпадает с системной настройкой, поэтому сохраненное значение удаляется и сайт снова следует системной настройке.
Переключатели с тремя состояниями
Если места достаточно и пользователи уже настроены на долгосрочные предпочтения, трехпозиционный переключатель может обеспечить большую ясность. В этом случае модель данных четко сопоставляется с тремя параметрами пользовательского интерфейса: системным, светлым и темным.
Чтобы снизить когнитивную нагрузку, в варианте "Системная тема" также должно быть указано, какая тема сейчас выбрана в системе. Например, это можно сделать так:
- Значок, например значок солнца в правом нижнем углу системного значка.
- Текст, например "Системная (сейчас светлая)"