Как определить тему пользователя (светлая или темная)
Что проверяют
Знаете ли вы стандартный механизм, а не определение темы по времени суток, и умеете ли совместить системную настройку с выбором пользователя без мигания при загрузке. Следом обычно спрашивают, как избежать вспышки светлой темы при SSR.
Как отвечать
Системное предпочтение читается медиазапросом prefers-color-scheme:
- в CSS:
@media (prefers-color-scheme: dark)переопределяет CSS-переменные на:root. Функцияlight-dark()в современных браузерах позволяет задать оба цвета в одной строке; - в JS:
window.matchMedia('(prefers-color-scheme: dark)').matches, а подписка на смену черезaddEventListener('change', ...). УстаревшийaddListenerнужен только очень старому Safari; - нативные элементы: свойство
color-scheme: light darkили<meta name="color-scheme" content="light dark">, иначе скроллбары, поля форм и чекбоксы останутся светлыми.
У темы три состояния, а не два: системная, светлая и тёмная. Явный выбор храните в localStorage и ставьте атрибут data-theme на <html>, а стили для него пишите так, чтобы он перебивал медиазапрос. Пока выбора нет, работает системная тема и следует за её сменой.
Чтобы не было вспышки неправильной темы, маленький синхронный скрипт в <head> читает localStorage и ставит атрибут до первой отрисовки. Сервер предпочтение не знает, поэтому при SSR нужна либо кука с выбором, либо Client Hint Sec-CH-Prefers-Color-Scheme, который есть только в Chromium. В React на <html> тогда ставят suppressHydrationWarning, потому что атрибут меняется до гидратации.
Где спотыкаются
Слушают change и перезаписывают тему, даже когда пользователь выбрал её вручную. Человек включил светлую, вечером система ушла в тёмную, и его выбор пропал.
Применяют тему в useEffect или после загрузки бандла. Первый кадр рисуется светлым, а потом страница мигает.
Меняют только фон и текст. Тени, рамки, картинки с белым фоном и графики в тёмной теме выглядят чужими, а контраст надо заново проверять по WCAG.