Гид по технологиям

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

6 min read Frontend Обновлено 30 Mar 2026
Тёмная тема в React — useState и useEffect
Тёмная тема в 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.

Решения:

  1. Серверная логика: если у вас аутентификация и пользовательские настройки хранятся в профиле на сервере, возвращайте тему вместе с HTML.
  2. Inline-скрипт в _document (Next.js): перед загрузкой React выполнить небольшой скрипт, который прочитает localStorage и сразу установит data-theme. Пример для _document.js:
// Пример упрощённый
// Вставьте этот скрипт в Head до загрузки основного бандла