useTheme
React hook provided by next-themes for accessing current theme state and controlling theme changes. Used throughout the tweakcn Theme Picker system.
This hook is provided by the next-themes library and re-exported from next-themes. It must be used within a ThemeProvider.
Hook Signature
import { useTheme } from "next-themes";
const {
theme,
themes,
setTheme,
systemTheme,
resolvedTheme,
forcedTheme
} = useTheme();
Return Values
The current active theme value (e.g., "catppuccin-dark", "vercel-light").Returns undefined during SSR or before hydration. Always check for undefined or use the mounted pattern.
Function to change the current theme. Accepts any value from the themes array.setTheme("catppuccin-dark"); // Switch to Catppuccin dark mode
Array of all available theme values configured in ThemeProvider.For tweakcn Theme Picker, this contains all 90 theme variants (45 themes × 2 modes).// ["default-light", "default-dark", "catppuccin-light", ...]
console.log(themes.length); // 90
systemTheme
'light' | 'dark' | undefined
The user’s OS-level theme preference ("light" or "dark").Not used in tweakcn Theme Picker since enableSystem={false} in the provider.
The actual theme being used after resolving system preferences.In tweakcn, this is the same as theme since system theme is disabled.
Theme value if forced by parent component configuration.Not used in tweakcn Theme Picker.
Basic Usage
Simple Theme Access
import { useTheme } from "next-themes";
function ThemeStatus() {
const { theme } = useTheme();
return <div>Current theme: {theme}</div>;
}
Theme Switching
import { useTheme } from "next-themes";
function ThemeButtons() {
const { setTheme } = useTheme();
return (
<div>
<button onClick={() => setTheme("catppuccin-dark")}>Catppuccin Dark</button>
<button onClick={() => setTheme("vercel-light")}>Vercel Light</button>
</div>
);
}
Advanced Usage
Hydration-Safe Theme Access
Prevent hydration mismatches by checking if the component has mounted:
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
function SafeThemeDisplay() {
const [mounted, setMounted] = useState(false);
const { theme } = useTheme();
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return <div>Loading theme...</div>;
}
return <div>Current theme: {theme}</div>;
}
Parsing Theme Name and Mode
Extract color theme and mode from the theme string:
import { useTheme } from "next-themes";
function parseTheme(theme: string | undefined) {
if (!theme) return { colorTheme: "default", mode: "dark" };
if (theme.endsWith("-dark")) {
return {
colorTheme: theme.replace("-dark", ""),
mode: "dark" as const
};
}
if (theme.endsWith("-light")) {
return {
colorTheme: theme.replace("-light", ""),
mode: "light" as const
};
}
return { colorTheme: "default", mode: "dark" as const };
}
function ThemeControls() {
const { theme, setTheme } = useTheme();
const { colorTheme, mode } = parseTheme(theme);
const toggleMode = () => {
const newMode = mode === "dark" ? "light" : "dark";
setTheme(`${colorTheme}-${newMode}`);
};
const changeColor = (newColor: string) => {
setTheme(`${newColor}-${mode}`);
};
return (
<div>
<p>Color: {colorTheme}, Mode: {mode}</p>
<button onClick={toggleMode}>Toggle Mode</button>
<button onClick={() => changeColor("catppuccin")}>Use Catppuccin</button>
</div>
);
}
Theme Validation
Ensure theme values are valid before setting:
import { useTheme } from "next-themes";
import { allThemeValues } from "@/lib/themes-config";
function SafeThemeSetter() {
const { setTheme, themes } = useTheme();
const safeSetTheme = (newTheme: string) => {
if (themes.includes(newTheme)) {
setTheme(newTheme);
} else {
console.error(`Invalid theme: ${newTheme}`);
}
};
return (
<button onClick={() => safeSetTheme("catppuccin-dark")}>
Switch Theme
</button>
);
}
Get Current Theme Configuration
Lookup the full theme config object:
import { useTheme } from "next-themes";
import { themes, type ThemeConfig } from "@/lib/themes-config";
function ThemeInfo() {
const { theme } = useTheme();
// Extract color theme name (remove -light/-dark)
const colorTheme = theme?.replace(/-light|-dark$/, "") || "default";
// Find theme config
const config = themes.find(t => t.name === colorTheme) || themes[0];
return (
<div>
<h3>{config.title}</h3>
<p>Font: {config.fontSans}</p>
<div>
<span>Light: {config.primaryLight}</span>
<span>Dark: {config.primaryDark}</span>
</div>
</div>
);
}
React to Theme Changes
Run side effects when theme changes:
import { useTheme } from "next-themes";
import { useEffect } from "react";
function ThemeLogger() {
const { theme } = useTheme();
useEffect(() => {
if (theme) {
console.log(`Theme changed to: ${theme}`);
// Analytics, etc.
}
}, [theme]);
return null;
}
Programmatic Theme Selection
Cycle through themes or implement custom logic:
import { useTheme } from "next-themes";
import { sortedThemes } from "@/lib/themes-config";
function ThemeCycler() {
const { theme, setTheme } = useTheme();
const { colorTheme, mode } = parseTheme(theme);
const cycleTheme = () => {
const currentIndex = sortedThemes.findIndex(t => t.name === colorTheme);
const nextIndex = (currentIndex + 1) % sortedThemes.length;
const nextTheme = sortedThemes[nextIndex];
setTheme(`${nextTheme.name}-${mode}`);
};
return (
<button onClick={cycleTheme}>
Next Theme
</button>
);
}
Common Patterns
Theme Persistence
Themes are automatically persisted to localStorage by next-themes:
// Automatically saved
localStorage.getItem("theme"); // "catppuccin-dark"
SSR Considerations
The theme value is undefined during SSR. Always handle this case:
function ThemeDisplay() {
const { theme } = useTheme();
// ❌ Bad: Can cause hydration mismatch
return <div>{theme}</div>;
// ✅ Good: Handle undefined
return <div>{theme || "Loading..."}</div>;
// ✅ Better: Use mounted state (see Hydration-Safe example)
}
Type Safety
Create typed helpers for better autocomplete:
import { useTheme as useNextTheme } from "next-themes";
import { allThemeValues } from "@/lib/themes-config";
type ThemeValue = typeof allThemeValues[number];
export function useTheme() {
const { theme, setTheme, ...rest } = useNextTheme();
return {
theme: theme as ThemeValue | undefined,
setTheme: (newTheme: ThemeValue) => setTheme(newTheme),
...rest
};
}
Requirements
This hook MUST be used inside a ThemeProvider. Using it outside will throw an error.
// ❌ Error: useTheme must be used within ThemeProvider
function App() {
const { theme } = useTheme(); // Error!
return <div>{theme}</div>;
}
// ✅ Correct: Inside ThemeProvider
function App() {
return (
<ThemeProvider>
<Content />
</ThemeProvider>
);
}
function Content() {
const { theme } = useTheme(); // Works!
return <div>{theme}</div>;
}
Full Example
Complete implementation using all major features:
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
import { themes, sortedThemes } from "@/lib/themes-config";
export function AdvancedThemeControl() {
const [mounted, setMounted] = useState(false);
const { theme, setTheme, themes: availableThemes } = useTheme();
useEffect(() => setMounted(true), []);
if (!mounted) {
return <div className="skeleton" />;
}
const { colorTheme, mode } = parseTheme(theme);
const currentConfig = themes.find(t => t.name === colorTheme) || themes[0];
return (
<div>
<h2>Theme Control Panel</h2>
{/* Current State */}
<div>
<p>Active: {currentConfig.title} ({mode})</p>
<div style={{ backgroundColor: mode === "dark" ? currentConfig.primaryDark : currentConfig.primaryLight }} />
</div>
{/* Mode Toggle */}
<button onClick={() => setTheme(`${colorTheme}-${mode === "dark" ? "light" : "dark"}`)}>
Toggle to {mode === "dark" ? "Light" : "Dark"}
</button>
{/* Color Theme Selector */}
<select
value={colorTheme}
onChange={(e) => setTheme(`${e.target.value}-${mode}`)}
>
{sortedThemes.map(t => (
<option key={t.name} value={t.name}>
{t.title}
</option>
))}
</select>
{/* Stats */}
<p>Total themes available: {availableThemes.length}</p>
</div>
);
}
See Also