Тёмная тема (dark mode toggle)
Переключатель светлой/тёмной темы через CSS custom properties и класс
data-themeна<html>— с сохранением в localStorage.
Задача
Нужна кнопка для переключения темы. Выбранная тема должна сохраняться между сессиями и учитывать системные предпочтения пользователя.
Решение
CSS — токены для двух тем:
/* Светлая тема (по умолчанию) */
:root {
--color-bg: #ffffff;
--color-text: #1e293b;
--color-surface: #f8fafc;
--color-border: #e2e8f0;
--color-primary: #3b82f6;
}
/* Тёмная тема */
[data-theme="dark"] {
--color-bg: #0f172a;
--color-text: #e2e8f0;
--color-surface: #1e293b;
--color-border: #334155;
--color-primary: #60a5fa;
}
body {
background: var(--color-bg);
color: var(--color-text);
transition: background 0.3s, color 0.3s;
}
HTML:
<button class="theme-toggle" id="themeToggle" aria-label="Переключить тему">
<span class="theme-toggle__icon" id="themeIcon">🌙</span>
</button>
JavaScript:
const html = document.documentElement;
const toggle = document.getElementById('themeToggle');
const icon = document.getElementById('themeIcon');
// Инициализация: localStorage > системные предпочтения > light
function getInitialTheme() {
const saved = localStorage.getItem('theme');
if (saved === 'dark' || saved === 'light') return saved;
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
return prefersDark ? 'dark' : 'light';
}
function applyTheme(theme) {
html.setAttribute('data-theme', theme);
icon.textContent = theme === 'dark' ? '☀️' : '🌙';
toggle.setAttribute('aria-label', theme === 'dark' ? 'Включить светлую тему' : 'Включить тёмную тему');
localStorage.setItem('theme', theme);
}
// Применить сразу (до DOMContentLoaded)
applyTheme(getInitialTheme);
toggle.addEventListener('click', () => {
const current = html.getAttribute('data-theme');
applyTheme(current === 'dark' ? 'light' : 'dark');
});
// Реагировать на изменение системной темы
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
if (!localStorage.getItem('theme')) {
applyTheme(e.matches ? 'dark' : 'light');
}
});
Использование CSS-переменных в компонентах:
.card {
background: var(--color-surface);
border: 1px solid var(--color-border);
color: var(--color-text);
}
Ключевые моменты
data-themeна<html>— все CSS-переменные переопределяются каскадом для всего документа.localStorageхранит выбор пользователя;matchMediaсчитывает системную тему как дефолт.- Применяй тему до загрузки CSS (
<script>в<head>) — избежишь мигания FOUC (flash of unstyled content). transition: background 0.3sнаbody— плавная смена темы без резкого переключения.
Варианты
- CSS
prefers-color-schemeбез JS — автоматическая тема без переключателя:@media (prefers-color-scheme: dark) { :root { --color-bg: #0f172a; } } - Для React —
useLocalStorageхук + Context для распространения темы.