Skip to main content

Theme Configurations

Create a new project using vite installation andinstall all the required packages.

Configure Dark/Light Theme​

This document provides a comprehensive guide to the theming system implemented in our application. The system allows for dynamic switching between light and dark themes, with persistence across user sessions.

Architecture Overview​

The theming system consists of the following components:

  1. Theme Types - TypeScript definitions for theme options
  2. ThemeContext - React context for sharing theme state
  3. ThemeProvider - Provider component that manages theme state
  4. useTheme Hook - Custom hook for consuming theme context
  5. ThemeToggle - UI component for switching themes
  6. Storage Utilities - Helper functions for theme persistence

Required Files​

/src/shared/component/theme
/src/shared/hooks/useTheme.ts
/src/shared/util/localStorageUtils.ts
/src/App.tsx

Core Components​

Theme Types​

export type Theme = "light" | "dark";

export interface ThemeContextType {
theme: Theme;
setTheme: (theme: Theme) => void;
}

These types define the available themes and the shape of the theme context.

ThemeContext​

import { createContext, useContext } from "react";
import { ThemeContextType } from "@/shared/component/theme/type";

export const ThemeContext = createContext<ThemeContextType | undefined>(
undefined
);

export function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error("useTheme must be used within a ThemeProvider");
}
return context;
}

The ThemeContext provides a React context for theme state, while the useTheme hook simplifies consumption.

ThemeProvider​

import { useEffect, useState } from "react";
import { localStorageUtil } from "@/shared/util";
import { Theme } from "@/shared/component/theme/type";
import { ThemeContext } from "@/shared/hooks";

export const ThemeProvider = ({ children }: { children: React.ReactNode }) => {
const [theme, setTheme] = useState<Theme>(() => {
if (typeof window === "undefined") return "light";
const storedTheme = localStorageUtil.get("THEME_MODE") as Theme | null;
const systemTheme = window.matchMedia("(prefers-color-scheme: dark").matches
? "dark"
: "light";
return storedTheme || systemTheme;
});

useEffect(() => {
const root = document.documentElement;
root.classList.remove("light", "dark");
root.classList.add(theme);
localStorageUtil.set("THEME_MODE", theme);
}, [theme]);

return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
};

The ThemeProvider component handles:

  • Initial theme detection from localStorage or system preference
  • Applying theme classes to the document
  • Persisting theme changes to localStorage
  • Providing theme state and setter to child components

ThemeToggle Component​

import { Moon, Sun } from "lucide-react";
import { useTheme } from "@/shared/hooks";
import { ActionButton } from "../component/UI/ActionButton";

export const ThemeToggle = () => {
const { theme, setTheme } = useTheme();
const toggleTheme = () => {
setTheme(theme === "light" ? "dark" : "light");
};

return (
<ActionButton
children={
theme === "light" ? (
<Moon className="size-5" />
) : (
<Sun className="size-5" />
)
}
aria-label="Theme"
onClick={toggleTheme}
className="size-10 rounded-full"
variant="ghost"
/>
);
};

The ThemeToggle component provides a user interface for toggling between themes, using appropriate icons from Lucide React.

Storage Utilities​

import { StorageKeys } from "@/shared/constants/storage/keys";

type Key = keyof typeof StorageKeys.LOCAL;

export const localStorageUtil = {
set: (key: Key, value: string) => {
localStorage.setItem(StorageKeys.LOCAL?.[key], value);
},
get: (key: Key): string | null => {
const value = localStorage.getItem(StorageKeys.LOCAL?.[key]);
return value ? value : null;
},
setUsingStringify: (key: Key, value: unknown) => {
localStorage.setItem(StorageKeys.LOCAL?.[key], JSON.stringify(value));
},
getUsingParse: (key: Key): unknown | null => {
const value = localStorage.getItem(StorageKeys.LOCAL?.[key]);
return value ? JSON.parse(value) : null;
},
remove: (key: Key) => {
localStorage.removeItem(StorageKeys.LOCAL?.[key]);
},
clear: () => {
localStorage.clear();
},
};

The storage utilities provide type-safe access to localStorage for theme persistence.

Storage Keys​

export const StorageKeys = {
// Local Storage Keys
LOCAL: {
// User preferences - persisted
THEME_MODE: "THEME_MODE",
},
// Session Storage Keys
SESSION: {},
} as const;

Defines constants for storage keys to prevent typos and ensure type safety.

Implementation Guide​

Setting Up the ThemeProvider​

Wrap your application with the ThemeProvider component to enable theming:

// In your root component (e.g., App.tsx or _app.tsx)
import { ThemeProvider } from "@/shared/component/theme/ThemeProvider";

function App() {
return (
<ThemeProvider>
<YourApp />
</ThemeProvider>
);
}

Using the Theme in Components​

Use the useTheme hook to access theme state in your components:

import { useTheme } from "@/shared/hooks";

function YourComponent() {
const { theme, setTheme } = useTheme();

return (
<div>
<p>Current theme: {theme}</p>
<button onClick={() => setTheme("light")}>Light</button>
<button onClick={() => setTheme("dark")}>Dark</button>
</div>
);
}

Adding the Theme Toggle​

Add the ThemeToggle component to your layout or navbar:

import { ThemeToggle } from "@/shared/component/theme/ThemeToggle";

function Navbar() {
return (
<nav>
<div className="logo">Your App</div>
<div className="controls">
<ThemeToggle />
</div>
</nav>
);
}

Styling for Themes​

Define theme-specific styles in your CSS using the class names applied by the ThemeProvider:

/* In your global CSS file */
:root {
--background: white;
--text: black;
}

.dark {
--background: #121212;
--text: white;
}

body {
background-color: var(--background);
color: var(--text);
}