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

Application Theming System

Guide to implementing theme switching (light/dark) using React Context, HOC, and localStorage.

Theme Management Architecture

Benefit: Isolation of theme logic from business components. Automatic saving of user choice and prevention of theme 'flicker' on load.

Themization File Structure

Location and Conventions

  • Provider:

    Located in src/app/providers/theme/. Responsible for global initialization.

  • Shared Logic:

    Context, hooks, and UI components are placed in shared/ as they are reusable.

01Types and Interfaces

Define allowed theme values (light, dark) and context structure for state management.
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

Creating a data channel for passing theme data down the component tree without prop drilling.
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);

03Custom Hook

Encapsulating context access logic in a convenient useTheme hook with provider presence validation.
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

Implementing a provider component that manages state, interacts with localStorage, and manipulates html classes.
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};

05Initialization HOC

Creating the withTheme Higher-Order Component for easy application wrapping at the provider level.
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 Component (Toggle)

Creating a toggle button that uses the useTheme hook to switch the visual state.
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};