Theme Toggle
.mdA sun/moon button that flips the whole page between dark and light with a View Transition reveal from the button, as a circle or staggered slats.
themedark modelight modeview transitionstoggleclip-pathhook
Installation
$npx shadcn@latest add @geturui/theme-toggle
Usage
usage.tsx
1import { ThemeToggle, useTheme, themeInitScript } from "@/components/ui/theme-toggle";
2
3// In app/layout.tsx <head>: avoid a flash of the wrong theme
4// <script dangerouslySetInnerHTML={{ __html: themeInitScript }} />
5
6export function Header() {
7 const { theme } = useTheme();
8 return (
9 <header className="flex items-center justify-between">
10 <span>{theme} mode</span>
11 <ThemeToggle variant="circle" />
12 </header>
13 );
14}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "circle" | "shutter" | "circle" | circle: a radial reveal from the button. shutter: vertical slats sweep in from the button's side. |
duration | number | 650 | Length of the reveal in ms. |
slats | number | 8 | Number of slats in the shutter variant. |
onThemeChange | (theme: "dark" | "light") => void | — | Called after the theme switches. |
className | string | — | Classes for the button. |
useTheme() | () => { theme; setTheme(t); toggle() } | — | Hook reading <html data-theme>, synced across components and tabs; writes persist to localStorage("theme"). |
Accessibility
- The button's label names the action (“Switch to light theme”) and updates after each toggle.
- Browsers without document.startViewTransition, and users with reduced motion, get an instant switch with no reveal.
- Keyboard and screen-reader users get the same result as a click: the attribute flips and the choice is saved.
- Export themeInitScript into <head> so the saved theme applies before first paint.