Добавление тёмной темы в React с помощью useState и useEffect

Коротко о задаче
Тёмный режим — это переключаемая тема, при которой интерфейс использует тёмную цветовую палитру вместо светлой. Задача: дать пользователю возможность выбирать тему, сохранять выбор и корректно применять стили во всём приложении.
Что важно знать в двух строчках
- useState хранит текущее состояние темы в клиентском приложении. useEffect реагирует на изменение и применяет класс к document.body или переменным CSS.
- Для сохранения между перезагрузками используйте localStorage; для SSR потребуется дополнительная логика на сервере.
Почему применяют тёмный режим
- Экономия энергии на OLED/AMOLED-экранах — тёмные пиксели потребляют меньше энергии.
- Снижение зрительной нагрузки в условиях низкой освещённости.
- Более выразительный визуальный контраст для контента, особенно в медиаприложениях.
Быстрая реализация: шаг за шагом
Ниже — минимальная рабочая реализация. Сначала создаём состояние темы и кнопку-переключатель.
import React, { useState } from 'react';
function App() {
const [theme, setTheme] = useState('light');
return (
Привет, мир!
);
}
export default App;Это простой пример: состояние theme по умолчанию “light” и класс применяется к корневому div.
Добавляем кнопку-переключатель
import React, { useState } from 'react';
function App() {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prev => (prev === 'light' ? 'dark' : 'light'));
};
return (
Привет, мир!
);
}
export default App;Здесь мы используем функциональное обновление состояния — это помогает избежать состояния гонки при нескольких быстрых кликах.
Применяем тему через useEffect
import React, { useState, useEffect } from 'react';
function App() {
const [theme, setTheme] = useState('light');
const toggleTheme = () => setTheme(prev => (prev === 'light' ? 'dark' : 'light'));
useEffect(() => {
document.body.className = theme;
}, [theme]);
return (
Привет, мир!
);
}
export default App;useEffect гарантирует, что при изменении theme класс на body обновится и связанные CSS-правила вступят в силу.
CSS для светлой и тёмной тем
Создайте файл darkMode.css и импортируйте его в приложение.
/* darkMode.css */
:root {
--bg-color: #ffffff;
--text-color: #222222;
}
.dark {
--bg-color: #1f2937; /* тёмный фон */
--text-color: #f9fafb; /* светлый текст */
}
.light {
--bg-color: #ffffff;
--text-color: #111827;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
transition: background-color 150ms ease, color 150ms ease;
}Использование CSS-переменных упрощает масштабирование темы на многие компоненты и библиотеки.
Импорт CSS в компонент
import React, { useState, useEffect } from 'react';
import './darkMode.css';
function App() {
const [theme, setTheme] = useState('light');
const toggleTheme = () => setTheme(prev => (prev === 'light' ? 'dark' : 'light'));
useEffect(() => {
document.body.className = theme;
}, [theme]);
return (
Привет, мир!
);
}
export default App;Запуск приложения
В терминале:
npm startОткройте приложение в браузере и нажмите кнопку «Переключить тему».

Сохранение выбора пользователя (localStorage)
Чтобы тема сохранялась после перезагрузки страницы, используйте localStorage при инициализации состояния и при его обновлении:
import React, { useState, useEffect } from 'react';
import './darkMode.css';
function App() {
const [theme, setTheme] = useState(() => localStorage.getItem('theme') || 'light');
const toggleTheme = () => setTheme(prev => (prev === 'light' ? 'dark' : 'light'));
useEffect(() => {
localStorage.setItem('theme', theme);
document.body.className = theme;
}, [theme]);
return (
Привет, мир!
);
}
export default App;Этот вариант безопасен и прост, но не учитывает SSR: localStorage существует только в браузере.

Дополнительные опции и лучшие практики
Поддержка системной темы (prefers-color-scheme)
Вы можете подставить предпочтение системы как дефолт:
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
const [theme, setTheme] = useState(() => localStorage.getItem('theme') || (prefersDark ? 'dark' : 'light'));Также в CSS можно реагировать напрямую:
@media (prefers-color-scheme: dark) {
:root { --bg-color: #111; --text-color: #fff; }
}Подход с data-атрибутом вместо класса
Иногда удобнее ставить data-theme на html или body:
document.documentElement.setAttribute('data-theme', theme);Это помогает, если у вас CSS-правила с высокой специфичностью или если вы используете сторонние библиотеки.
Использование Context API
Для доступа к теме из любого компонента лучше обернуть приложение в ThemeProvider:
import React, { createContext, useContext, useState, useEffect } from 'react';
const ThemeContext = createContext();
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState(() => localStorage.getItem('theme') || 'light');
useEffect(() => {
localStorage.setItem('theme', theme);
document.body.className = theme;
}, [theme]);
return (
{children}
);
}
export const useTheme = () => useContext(ThemeContext);В компонентах можно вызывать useTheme() и получать текущую тему и сеттер.
Интеграция с CSS-in-JS и Tailwind
- Для styled-components: меняйте тему через ThemeProvider библиотеки и переключайте значения переменных.
- Для Tailwind: используйте плагин dark mode (class или media), и переключайте класс на элементе .
Вопросы доступности и UX
- Контраст: убедитесь, что текст и элементы управления соответствуют рекомендациям WCAG (минимально AA для основного текста).
- Индикация: явная иконка/метка состояния темы поможет пользователю понять текущую настройку.
- Предпочтение без анимации: уважайте prefers-reduced-motion при добавлении переходов.
SSR и Next.js: как избежать мерцания (FOUC) и рассинхронизации
Проблема: при рендере на сервере localStorage нет, поэтому цветовая тема может измениться после гидратации — возникает FOUC.
Решения:
- Серверная логика: если у вас аутентификация и пользовательские настройки хранятся в профиле на сервере, возвращайте тему вместе с HTML.
- Inline-скрипт в _document (Next.js): перед загрузкой React выполнить небольшой скрипт, который прочитает localStorage и сразу установит data-theme. Пример для _document.js:
// Пример упрощённый
// Вставьте этот скрипт в Head до загрузки основного бандла
Этот подход уменьшает мерцание, но требует аккуратности и безопасной сериализации.
Кейс: когда этот подход может не сработать
- Если приложение полностью рендерится на сервере и theme управляется только на клиенте без серверной поддержки — возможен FOUC.
- Если вы используете CSS-in-JS с изоляцией стилей (например, Shadow DOM), глобальный класс на body может не влиять на компоненты.
- В старых браузерах без поддержки CSS-переменных придётся писать запасные стили.
Полезные шаблоны и чеклист для команды
Чеклист при добавлении тёмной темы в проект:
- Решить: классы на body или data-атрибуты на html?
- Выбрать стратегию хранения (localStorage / cookie / сервер).
- Добавить поддержку prefers-color-scheme как fallback.
- Обновить дизайн-систему: цветовые токены как CSS-переменные.
- Проверить контрасты и accessibility.
- Написать e2e-тесты на переключение темы и сохранение.
- Обеспечить SSR-инициализацию темы (если применимо).
Роль‑ориентированные задачи:
- Дизайнер: подготовить палитры и токены для светлой и тёмной тем.
- Фронтенд‑разработчик: реализовать Provider/контекст и шаблонные стили.
- QA: проверить визуальное соответствие и accessibility, убедиться в отсутствии FOUC.
Критерии приёмки
- Тема переключается через видимый контрол и через системную настройку.
- Выбор пользователя сохраняется после перезагрузки.
- Нельзя наблюдать мерцание темы при загрузке (для SSR — нет FOUC).
- Контраст текста соответствует рекомендациям WCAG.
Тесты и сценарии приёмки
- Начальная загрузка без сохранённой темы: отображается светлая или системная тема.
- Пользователь переключает тему — стиль меняется немедленно.
- Перезагрузка: выбранная тема сохраняется.
- Для пользователей с prefers-reduced-motion переключения проходят без анимаций.
Альтернативные подходы и рекомендации
- Использовать CSS-переменные + system-level prefers-color-scheme как базу и localStorage для персистентности.
- Для больших приложений — хранить значение в профиле пользователя на сервере.
- Если используете серверный рендеринг, предпочтительнее синхронизировать тему до гидратации.
Ментальные модели и эвристики
- “Глобальный токен цвета” — держите палитру в одном месте (переменные), чтобы можно было переключать тему без правки множества компонентов.
- “Дефолт лучше системного” — по умолчанию используйте системное предпочтение, но уважайте выбор пользователя.
- “Минимум побочных эффектов” — применяйте тему через data-theme или root-класс и минимизируйте прямые inline-стили.
Короткая инструкция для анонса (100–200 слов)
Добавили поддержку тёмной темы в приложение на React: теперь пользователи могут переключаться между светлой и тёмной темами, а выбор сохраняется между сессиями. Реализация использует хуки useState и useEffect для управления состоянием и применения темы, CSS-переменные для централизованного управления палитрой и localStorage для персистентности. Для приложений с серверным рендерингом показаны способы избежать мерцания темы при загрузке, а также даны рекомендации по доступности и интеграции с системными настройками prefers-color-scheme.
Итог
Добавление тёмной темы в React — простая задача, если начать с корректной архитектуры: хранение значения, применение стилей через корневой класс/атрибут и сохранение выбора. Для масштабных проектов используйте CSS-переменные, ThemeProvider и учитывайте SSR, чтобы избежать мерцания. При разработке не забывайте про контраст и поддержку пользователей с особыми потребностями.
Important: проверьте поведение темы на реальных устройствах и в условиях с низким энергопотреблением для OLED-экрана.
Похожие материалы
CSS font-family: как менять шрифты на сайте
График амортизации кредита в Excel — пошагово
Разгон Raspberry Pi 4 — безопасный пошаговый гид
Как запустить Windows 11 на Mac — варианты и советы
Мошенничество с возвратом средств через техподдержку