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:
- Theme Types - TypeScript definitions for theme options
- ThemeContext - React context for sharing theme state
- ThemeProvider - Provider component that manages theme state
- useTheme Hook - Custom hook for consuming theme context
- ThemeToggle - UI component for switching themes
- 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);
}