# Mode Toggle

Cycles light, dark, and system theme modes and persists the choice.

## Installation

```bash
npx shadcn@latest add https://reactdocs.canceydejean.dev/r/mode-toggle.json
```

[Registry JSON](https://reactdocs.canceydejean.dev/r/mode-toggle.json)

## Preview

```tsx
import { ModeToggle } from "@/lib/mode-toggle";

export function Preview() {
  return (
    <div className="flex flex-col items-center gap-3">
      <ModeToggle />
      <p className="text-sm text-muted-foreground">Cycles light, dark, and system.</p>
    </div>
  );
}
```


## Source

### lib/mode-toggle.tsx

```tsx
"use client";

import { Monitor, Moon, Sun } from "lucide-react";
import { useEffect, useState } from "react";

type ThemeMode = "light" | "dark" | "auto";

function getInitialMode(): ThemeMode {
  if (typeof window === "undefined") {
    return "auto";
  }

  const stored = window.localStorage.getItem("theme");
  if (stored === "light" || stored === "dark" || stored === "auto") {
    return stored;
  }

  return "auto";
}

function applyThemeMode(mode: ThemeMode) {
  const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
  const resolved = mode === "auto" ? (prefersDark ? "dark" : "light") : mode;

  document.documentElement.classList.remove("light", "dark");
  document.documentElement.classList.add(resolved);

  if (mode === "auto") {
    document.documentElement.removeAttribute("data-theme");
  } else {
    document.documentElement.setAttribute("data-theme", mode);
  }

  document.documentElement.style.colorScheme = resolved;
}

function ModeToggle({ className }: { className?: string }) {
  const [mode, setMode] = useState<ThemeMode>("auto");

  useEffect(() => {
    const initialMode = getInitialMode();
    setMode(initialMode);
    applyThemeMode(initialMode);
  }, []);

  useEffect(() => {
    if (mode !== "auto") {
      return undefined;
    }

    const media = window.matchMedia("(prefers-color-scheme: dark)");
    const onChange = () => applyThemeMode("auto");

    media.addEventListener("change", onChange);
    return () => {
      media.removeEventListener("change", onChange);
    };
  }, [mode]);

  function toggleMode() {
    const nextMode: ThemeMode = mode === "light" ? "dark" : mode === "dark" ? "auto" : "light";
    setMode(nextMode);
    applyThemeMode(nextMode);
    window.localStorage.setItem("theme", nextMode);
  }

  const label =
    mode === "auto"
      ? "Theme mode: auto (system). Click to switch to light mode."
      : `Theme mode: ${mode}. Click to switch mode.`;

  const Icon = mode === "auto" ? Monitor : mode === "dark" ? Moon : Sun;

  return (
    <button
      type="button"
      onClick={toggleMode}
      aria-label={label}
      title={label}
      data-slot="mode-toggle"
      data-mode={mode}
      className={
        className ??
        "inline-flex size-9 items-center justify-center rounded-full border border-border bg-background text-foreground shadow-sm transition-colors hover:bg-muted"
      }
    >
      <Icon className="size-4" aria-hidden="true" />
    </button>
  );
}

export { ModeToggle, type ThemeMode };
export default ModeToggle;
```



## Usage

A client button that cycles `light` → `dark` → `auto` (system preference), writes `theme` to
`localStorage`, and updates `document.documentElement` classes and `color-scheme`.

```tsx
import { ModeToggle } from "@/lib/mode-toggle";

<header className="flex justify-end p-4">
  <ModeToggle />
</header>
```

Icons switch to Sun, Moon, or Monitor based on the current mode. Pass `className` to restyle the
control. Store key is `theme` with values `light`, `dark`, or `auto`.

