useTheme Hook
info
What this hook provides:
- Access to the current theme (
'light'or'dark') - A function to toggle between light and dark themes
- Automatic persistence in localStorage
- Automatic application of theme classes to the document root
Dependencies
react(useState, useEffect)- Browser APIs:
localStorage,matchMedia,document.documentElement
Basic usage
import { useTheme } from '@hooks/useTheme';
export default function ThemeAwareComponent() {
const { theme, toggleTheme } = useTheme();
return (
<div>
<p>Current theme: {theme}</p>
<button onClick={toggleTheme}>Toggle theme</button>
</div>
);
}
Return value
The hook returns an object with two properties:
| Property | Type | Description |
|---|---|---|
theme | string | The current active theme (light or dark) |
toggleTheme | () => void | Function to toggle between light and dark themes |
How it works
Initialization
When the component first mounts, useTheme determines the initial theme in this order:
- localStorage: If a
'theme'key exists in localStorage, that value is used - System preference: If no saved preference exists, it checks the user's system preference using
matchMedia('(prefers-color-scheme: dark)') - Default fallback: If neither is available, it defaults to
'light'
Theme application
The hook uses a useEffect to:
- Remove any existing theme classes (
'light'or'dark') fromdocument.documentElement - Add the current theme as a class to the root element
- Save the theme preference to localStorage
This ensures that CSS variables defined in :root.light or :root.dark are properly applied.
Usage with CSS variables
The hook works in conjunction with CSS variables defined in /src/global-styles/variables.css:
/* Applied when theme is 'light' - Warm Hedgehog Palette */
:root.light {
--color-bg: #f8eacd; /* Soft cream */
--color-text: #2c1a1a; /* Dark brown */
--color-primary: #6a3817; /* Warm brown */
/* ... more variables */
}
/* Applied when theme is 'dark' - Deep Forest Night */
:root.dark {
--color-bg: #1a0f1f; /* Deep purple/brown */
--color-text: #f8eacd; /* Cream (inverted) */
--color-primary: #e0ad7d; /* Warm coral */
/* ... more variables */
}
See CSS Theme Variables for a complete list of available variables.
Examples
Simple theme toggle button
import { useTheme } from '@hooks/useTheme';
import { Moon, Sun } from 'lucide-react';
function ThemeToggle() {
const { theme, toggleTheme } = useTheme();
return (
<button onClick={toggleTheme} aria-label="Toggle theme">
{theme === 'dark' ? <Sun /> : <Moon />}
</button>
);
}
Conditional rendering based on theme
import { useTheme } from '@hooks/useTheme';
function ThemedLogo() {
const { theme } = useTheme();
return <img src={theme === 'dark' ? '/logo-dark.svg' : '/logo-light.svg'} alt="Logo" />;
}
Reading current theme without toggling
import { useTheme } from '@hooks/useTheme';
function ThemeInfo() {
const { theme } = useTheme();
return <p className="theme-indicator">You are viewing in {theme} mode</p>;
}
Implementation details
State management
const [theme, setTheme] = useState(() => {
if (typeof window === 'undefined') {
return 'light'; // SSR fallback
}
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
return savedTheme;
}
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
});
Toggle function
const toggleTheme = () => {
setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light'));
};
Side effects
useEffect(() => {
if (typeof window === 'undefined' || typeof document === 'undefined') {
return;
}
const root = document.documentElement;
// Remove both theme classes
root.classList.remove('light', 'dark');
// Add current theme class
root.classList.add(theme);
// Persist to localStorage
localStorage.setItem('theme', theme);
}, [theme]);
Testing
The hook can be mocked in tests:
import { vi } from 'vitest';
import * as useThemeModule from '@hooks/useTheme';
// Mock the hook
vi.spyOn(useThemeModule, 'useTheme').mockReturnValue({
theme: 'dark',
toggleTheme: vi.fn(),
});
See ThemeSwitch tests for examples of testing components that use this hook.
Related
- ThemeSwitch Component - UI component built with this hook
- CSS Theme Variables - Available CSS variables for theming