Theme Context
React · Example / how-to
Theme Context
React · Example / how-to
📋 Overview
Share light/dark theme across the tree with createContext, a provider, and a small useTheme hook.
🔧 Core concepts
| Piece | Role |
|---|---|
| Context | Avoid prop drilling |
| Provider | Own theme state |
| Custom hook | Enforce provider usage |
data-theme | Drive CSS variables |
💡 Examples
theme_context.tsx:
import {
createContext,
useContext,
useEffect,
useState,
type ReactNode,
} from "react";
type Theme = "light" | "dark";
type ThemeContextValue = {
theme: Theme;
toggle: () => void;
};
const ThemeContext = createContext<ThemeContextValue | null>(null);
export function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<Theme>("light");
useEffect(() => {
document.documentElement.dataset.theme = theme;
}, [theme]);
const value: ThemeContextValue = {
theme,
toggle: () => setTheme((t) => (t === "light" ? "dark" : "light")),
};
return (
<ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
);
}
export function useTheme() {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error("useTheme must be used within ThemeProvider");
return ctx;
}
export function ThemeToggle() {
const { theme, toggle } = useTheme();
return (
<button type="button" onClick={toggle}>
Theme: {theme}
</button>
);
}App wiring:
import { ThemeProvider, ThemeToggle } from "./theme_context";
export default function App() {
return (
<ThemeProvider>
<ThemeToggle />
</ThemeProvider>
);
}⚠️ Pitfalls
- Creating context value inline without memo can re-render broadly — fine for tiny apps; split if hot.
- Reading context outside the provider should throw a clear error.
- Persist theme separately (e.g. localStorage) if you need reload survival.