Code Reference

Theme Context

React · Example / how-to

Theme Context

React · Example / how-to


📋 Overview

Share light/dark theme across the tree with createContext, a provider, and a small useTheme hook.

🔧 Core concepts

PieceRole
ContextAvoid prop drilling
ProviderOwn theme state
Custom hookEnforce provider usage
data-themeDrive CSS variables

💡 Examples

theme_context.tsx:

import {
  createContext,
  useContext,
  useEffect,
  useState,
  type ReactNode,
} from "react";

type Theme = "light" | "dark";

type ThemeContextValue = {
  theme: Theme;
  toggle: () => void;
};

const ThemeContext = createContext<ThemeContextValue | null>(null);

export function ThemeProvider({ children }: { children: ReactNode }) {
  const [theme, setTheme] = useState<Theme>("light");

  useEffect(() => {
    document.documentElement.dataset.theme = theme;
  }, [theme]);

  const value: ThemeContextValue = {
    theme,
    toggle: () => setTheme((t) => (t === "light" ? "dark" : "light")),
  };

  return (
    <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
  );
}

export function useTheme() {
  const ctx = useContext(ThemeContext);
  if (!ctx) throw new Error("useTheme must be used within ThemeProvider");
  return ctx;
}

export function ThemeToggle() {
  const { theme, toggle } = useTheme();
  return (
    <button type="button" onClick={toggle}>
      Theme: {theme}
    </button>
  );
}

App wiring:

import { ThemeProvider, ThemeToggle } from "./theme_context";

export default function App() {
  return (
    <ThemeProvider>
      <ThemeToggle />
    </ThemeProvider>
  );
}

⚠️ Pitfalls

  • Creating context value inline without memo can re-render broadly — fine for tiny apps; split if hot.
  • Reading context outside the provider should throw a clear error.
  • Persist theme separately (e.g. localStorage) if you need reload survival.

On this page