Platform
    • Naming & Linter Rules
    • Styling Standards
    • Theme
    • Zod & Typing
    • Internationalization
Projects
  • API Connections & Tools
  1. Documentation
  2. Theme Master Guide

Система тем приложения

Руководство по реализации переключения тем (light/dark) с использованием React Context, HOC и localStorage.

Архитектура управления темой

Преимущество: Изоляция логики темы от бизнес-компонентов. Автоматическое сохранение выбора пользователя и отсутствие 'мигания' при загрузке.

Структура файлов Темизации

Расположение и Соглашения

  • Провайдер:

    Находится в src/app/providers/theme/. Отвечает за глобальную инициализацию.

  • Shared Logic:

    Контекст, хуки и UI-компонент выносятся в shared/, так как они переиспользуемы.

01Типы и Интерфейсы

Определяем допустимые значения темы (light, dark) и структуру контекста для управления состоянием.
shared/ui/layout/theme-toggle/model/theme.types.ts
1export type TTheme = "light" | "dark"; 2 3export interface IThemeContextType { 4 theme: TTheme; 5 toggleTheme: () => void; 6}

02React Context

Создаем канал передачи данных темы через дерево компонентов без использования пропсов.
shared/ui/layout/theme-toggle/model/theme.context.ts
1import { createContext } from "react"; 2import { IThemeContextType } from "./theme.types"; 3 4export const ThemeContext = createContext<IThemeContextType | undefined>(undefined);

03Кастомный Хук

Инкапсулируем логику доступа к контексту в удобном хуке useTheme с проверкой на наличие провайдера.
shared/ui/layout/theme-toggle/model/useTheme.tsx
1import { useContext } from "react"; 2import { ThemeContext } from "../context/theme.context"; 3 4export const useTheme = () => { 5 const context = useContext(ThemeContext); 6 if (!context) { 7 throw new Error("useTheme must be used within a ThemeProvider"); 8 } 9 return context; 10};

04Theme Provider

Реализуем компонент-провайдер, который управляет стейтом, работает с localStorage и манипулирует классами на html.
app/providers/theme/theme.provider.tsx
1import React, { useCallback, useEffect, useMemo, useState } from "react"; 2import { type TTheme, ThemeContext } from "@/shared/ui/layout/theme-toggle/model"; 3 4export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { 5 const [theme, setTheme] = useState<TTheme>("light"); 6 const [mounted, setMounted] = useState(false); 7 8 useEffect(() => { 9 const saved = localStorage.getItem("theme") as TTheme; 10 if (saved) setTheme(saved); 11 setMounted(true); 12 }, []); 13 14 const toggleTheme = useCallback(() => { 15 setTheme((prev) => { 16 const next = prev === "light" ? "dark" : "light"; 17 localStorage.setItem("theme", next); 18 return next; 19 }); 20 }, []); 21 22 useEffect(() => { 23 if (!mounted) return; 24 document.documentElement.classList.remove("light", "dark"); 25 document.documentElement.classList.add(theme); 26 }, [theme, mounted]); 27 28 const value = useMemo(() => ({ theme, toggleTheme }), [theme, toggleTheme]); 29 30 return ( 31 <ThemeContext.Provider value={value}> 32 {children} 33 </ThemeContext.Provider> 34 ); 35};

05HOC для инициализации

Создаем withTheme Higher-Order Component для удобного оборачивания приложения на уровне провайдеров.
app/providers/theme/with-theme.tsx
1import React from "react"; 2import { ThemeProvider } from "./theme.provider"; 3 4export const withTheme = <P extends object>(Component: React.ComponentType<P>) => { 5 return (props: P) => ( 6 <ThemeProvider> 7 <Component {...props} /> 8 </ThemeProvider> 9 ); 10};

06UI Компонент (Toggle)

Создаем кнопку-переключатель, которая использует хук useTheme для смены визуального стейта.
shared/ui/layout/theme-toggle/ui/theme-toggle.tsx
1import { Moon, Sun } from "lucide-react"; 2import { Button } from "@/shared/ui"; 3import { useTheme } from "../model"; 4 5export const ThemeToggle = () => { 6 const { theme, toggleTheme } = useTheme(); 7 8 return ( 9 <Button onClick={toggleTheme} variant="outline" size="sm"> 10 {theme === "light" ? <Moon size={16} /> : <Sun size={16} />} 11 </Button> 12 ); 13};