The useTheme hook from next-themes provides programmatic access to the current theme and methods to change it. Use this hook to build custom theme selectors or implement theme-aware components.
Installation
The hook is available through next-themes, which is automatically installed with the theme system:
npx shadcn@latest add https://tweakcn-picker.vercel.app/r/nextjs/theme-system.json
Basic Usage
import { useTheme } from "next-themes" ;
export function MyComponent () {
const { theme , setTheme } = useTheme ();
return (
< div >
< p > Current theme: { theme } </ p >
< button onClick = { () => setTheme ( "catppuccin-dark" ) } >
Switch to Catppuccin Dark
</ button >
</ div >
);
}
Return Values
The useTheme hook returns an object with the following properties:
The current theme value (e.g., "catppuccin-dark", "vercel-light"). Will be undefined during initial render before hydration.
Function to change the current theme. Pass any valid theme value like "cyberpunk-dark" or "nature-light".
Array of all available theme values from the ThemeProvider. Contains all installed themes in both light and dark variants.
systemTheme
'light' | 'dark' | undefined
The system’s color scheme preference. Always undefined in tweakcn theme system because enableSystem={false} in the provider.
The actual theme being displayed. Same as theme in the tweakcn system.
Examples
Get Current Theme
import { useTheme } from "next-themes" ;
export function CurrentThemeDisplay () {
const { theme } = useTheme ();
return < p > Current theme: { theme } </ p > ;
}
Switch Theme
import { useTheme } from "next-themes" ;
export function ThemeButton () {
const { setTheme } = useTheme ();
return (
< button onClick = { () => setTheme ( "cyberpunk-dark" ) } >
Use Cyberpunk Theme
</ button >
);
}
Toggle Light/Dark Mode
Preserve the color theme while toggling between light and dark:
import { useTheme } from "next-themes" ;
import { Moon , Sun } from "lucide-react" ;
export function ModeToggle () {
const { theme , setTheme } = useTheme ();
// Parse current theme: "catppuccin-dark" -> "catppuccin"
const colorTheme = theme ?. replace ( /-light $| -dark $ / , "" ) || "default" ;
const isDark = theme ?. endsWith ( "-dark" ) ?? true ;
const toggleMode = () => {
const newMode = isDark ? "light" : "dark" ;
setTheme ( ` ${ colorTheme } - ${ newMode } ` );
};
return (
< button onClick = { toggleMode } >
{ isDark ? < Moon /> : < Sun /> }
{ isDark ? "Dark" : "Light" } Mode
</ button >
);
}
List All Available Themes
import { useTheme } from "next-themes" ;
export function ThemeList () {
const { themes } = useTheme ();
return (
< ul >
{ themes . map (( themeName ) => (
< li key = { themeName } > { themeName } </ li >
)) }
</ ul >
);
}
Simple Theme Selector
Create a basic dropdown selector:
import { useTheme } from "next-themes" ;
export function SimpleThemeSelector () {
const { theme , setTheme , themes } = useTheme ();
return (
< select value = { theme } onChange = { ( e ) => setTheme ( e . target . value ) } >
{ themes . map (( themeName ) => (
< option key = { themeName } value = { themeName } >
{ themeName }
</ option >
)) }
</ select >
);
}
Theme-Aware Component
Conditionally render based on the current theme:
import { useTheme } from "next-themes" ;
export function BrandLogo () {
const { theme } = useTheme ();
const isDark = theme ?. endsWith ( "-dark" );
return (
< img
src = { isDark ? "/logo-dark.svg" : "/logo-light.svg" }
alt = "Logo"
/>
);
}
Parse Theme Components
Extract theme name and mode from the theme string:
import { useTheme } from "next-themes" ;
function parseTheme ( theme : string | undefined ) {
if ( ! theme ) return { name: "default" , mode: "dark" };
if ( theme . endsWith ( "-dark" )) {
return { name: theme . replace ( "-dark" , "" ), mode: "dark" as const };
}
if ( theme . endsWith ( "-light" )) {
return { name: theme . replace ( "-light" , "" ), mode: "light" as const };
}
return { name: "default" , mode: "dark" as const };
}
export function ThemeInfo () {
const { theme } = useTheme ();
const { name , mode } = parseTheme ( theme );
return (
< div >
< p > Theme: { name } </ p >
< p > Mode: { mode } </ p >
</ div >
);
}
Switch Color Theme (Preserve Mode)
Change the color theme while keeping the current light/dark mode:
import { useTheme } from "next-themes" ;
import { themes } from "@/lib/themes-config" ;
export function ColorThemeSelector () {
const { theme , setTheme } = useTheme ();
// Extract current mode
const currentMode = theme ?. endsWith ( "-dark" ) ? "dark" : "light" ;
const handleThemeChange = ( themeName : string ) => {
setTheme ( ` ${ themeName } - ${ currentMode } ` );
};
return (
< div >
{ themes . map (( t ) => (
< button key = { t . name } onClick = { () => handleThemeChange ( t . name ) } >
{ t . title }
</ button >
)) }
</ div >
);
}
Theme Persistence
Theme selection is automatically persisted to localStorage by next-themes. The stored value persists across browser sessions:
import { useTheme } from "next-themes" ;
export function ThemeStatus () {
const { theme } = useTheme ();
// Check localStorage (client-side only)
const storedTheme = typeof window !== "undefined"
? localStorage . getItem ( "theme" )
: null ;
return (
< div >
< p > Current: { theme } </ p >
< p > Stored: { storedTheme } </ p >
</ div >
);
}
Hydration Handling
The theme value is undefined during server-side rendering and initial client render. Always handle this case:
import { useTheme } from "next-themes" ;
import { useEffect , useState } from "react" ;
export function SafeThemeComponent () {
const [ mounted , setMounted ] = useState ( false );
const { theme } = useTheme ();
useEffect (() => {
setMounted ( true );
}, []);
if ( ! mounted ) {
return < div > Loading... </ div > ;
}
return < div > Current theme: { theme } </ div > ;
}
Or use optional chaining:
import { useTheme } from "next-themes" ;
export function ThemeDisplay () {
const { theme } = useTheme ();
return < div > Current theme: { theme ?? "loading..." } </ div > ;
}
Theme Naming Convention
All tweakcn themes follow the pattern {name}-{mode}:
// Valid theme values:
"default-light" // ✓
"default-dark" // ✓
"catppuccin-light" // ✓
"cyberpunk-dark" // ✓
// Invalid (will not work):
"default" // ✗ Missing mode
"catppuccin" // ✗ Missing mode
"dark" // ✗ Missing name
When building custom selectors, always include both the theme name and mode:
setTheme ( ` ${ themeName } - ${ mode } ` ); // ✓ Correct
setTheme ( themeName ); // ✗ Incorrect
Using with Theme Config
Combine with themes-config.ts for rich theme metadata:
import { useTheme } from "next-themes" ;
import { themes } from "@/lib/themes-config" ;
export function ThemePreview () {
const { theme , setTheme } = useTheme ();
const currentMode = theme ?. endsWith ( "-dark" ) ? "dark" : "light" ;
return (
< div className = "grid grid-cols-3 gap-4" >
{ themes . map (( t ) => (
< button
key = { t . name }
onClick = { () => setTheme ( ` ${ t . name } - ${ currentMode } ` ) }
className = "flex items-center gap-2 p-3 border rounded-lg"
>
< div
className = "h-6 w-6 rounded-full"
style = { {
backgroundColor:
currentMode === "dark" ? t . primaryDark : t . primaryLight ,
} }
/>
< div className = "text-left" >
< p className = "font-medium" > { t . title } </ p >
< p className = "text-xs text-muted-foreground" > { t . fontSans } </ p >
</ div >
</ button >
)) }
</ div >
);
}
Next.js Server Components
The useTheme hook only works in Client Components. Mark your component with "use client":
"use client" ;
import { useTheme } from "next-themes" ;
export function ClientThemeComponent () {
const { theme , setTheme } = useTheme ();
// ...
}
If you need theme data in Server Components, access it via cookies (advanced):
import { cookies } from "next/headers" ;
export function ServerThemeComponent () {
const theme = cookies (). get ( "theme" )?. value ?? "default-dark" ;
// Server-side theme access (read-only)
}
Server-side theme access is read-only. Use client components with useTheme for interactive theme switching.
ThemeSwitcher Pre-built theme selector component
Custom Selector Build your own theme selector