Skip to content
GetUrUI

Theme Toggle

.md

A 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
Download source (.zip)

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

PropTypeDefaultDescription
variant"circle" | "shutter""circle"circle: a radial reveal from the button. shutter: vertical slats sweep in from the button's side.
durationnumber650Length of the reveal in ms.
slatsnumber8Number of slats in the shutter variant.
onThemeChange(theme: "dark" | "light") => void—Called after the theme switches.
classNamestring—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.

Related