Skip to main content

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

theme
string | undefined
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.
setTheme
(theme: string) => void
Function to change the current theme. Accepts any value from the themes array.
setTheme("catppuccin-dark"); // Switch to Catppuccin dark mode
themes
string[]
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.
resolvedTheme
string | undefined
The actual theme being used after resolving system preferences.In tweakcn, this is the same as theme since system theme is disabled.
forcedTheme
string | undefined
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

Build docs developers (and LLMs) love