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);
}