Skip to main content
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:
theme
string | undefined
The current theme value (e.g., "catppuccin-dark", "vercel-light"). Will be undefined during initial render before hydration.
setTheme
(theme: string) => void
Function to change the current theme. Pass any valid theme value like "cyberpunk-dark" or "nature-light".
themes
string[]
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.
resolvedTheme
string | undefined
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

Build docs developers (and LLMs) love