ThemeToggle
A light/dark mode toggle button with animated sun/moon icons. Works uncontrolled with localStorage persistence, or controlled via props. Applies the .dark class to <html> by default.
$npx shadcn add @patchui/theme-toggle
Default
Click to toggle the site theme. Persists to localStorage.
Sizes
Controlled
Current: light
Usage
import { ThemeToggle } from "@patchui/react";
// Uncontrolled - persists to localStorage, applies .dark class
<ThemeToggle />
// Controlled
const [theme, setTheme] = useState<"light" | "dark">("light");
<ThemeToggle
theme={theme}
onThemeChange={setTheme}
/>
// With next-themes
import { useTheme } from "next-themes";
function MyToggle() {
const { resolvedTheme, setTheme } = useTheme();
return (
<ThemeToggle
theme={resolvedTheme as "light" | "dark"}
onThemeChange={setTheme}
applyClass={false}
storageKey={false}
/>
);
}Flash Prevention
Add this inline script to your <head> to prevent a flash of wrong theme on page load:
<script>
(function() {
var t = localStorage.getItem('patch-ui-theme');
if (!t) t = matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
if (t === 'dark') document.documentElement.classList.add('dark');
})();
</script>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| theme | "light" | "dark" | "system" | - | Controlled theme value. Omit for uncontrolled mode. |
| onThemeChange | (theme: ResolvedTheme) => void | - | Called when the theme changes. |
| storageKey | string | false | "patch-ui-theme" | localStorage key for persistence. Set to false to disable. |
| applyClass | boolean | true | Whether to toggle the .dark class on <html>. |
| size | "sm" | "md" | "lg" | "md" | Size of the toggle button. |
Notes
- Uncontrolled mode: By default, ThemeToggle manages its own state, reads from localStorage on mount, and applies the
.darkclass to<html>. No provider needed. - Controlled mode: Pass
themeandonThemeChangefor external control (e.g. withnext-themes). SetapplyClass={false}andstorageKey={false}to avoid conflicts. - SSR safe: Uses
useSyncExternalStoreto detect mount state. Renders a blank placeholder before hydration to prevent mismatch. - Animation: Icon transitions use a CSS keyframe (
patch-theme-icon-in) defined in the patch-ui tokens CSS. - System preference: Pass
theme="system"to resolve based onprefers-color-scheme.