Theme Toggle
Switch between a sun and crescent moon inside one circular button.
Light mode
Install with the shadcn CLI
npx shadcn@4.21.5 add @cavemio/theme-toggleFirst install? Configure @cavemio once
Run npx shadcn@4.21.5 init if shadcn is not set up, then merge this entry into the registries field in your existing components.json. Keep your other settings and registries.
{
"registries": {
"@cavemio": "https://registry.cavemio.dev/r/cavemio-{name}.json"
}
}Overview
Switch between a sun and crescent moon inside one circular button.
Optional pageTransition="circular" reveals the new page theme from the button while the icon morphs. Apply your theme class, data attribute, or synchronous React state inside onThemeChange; the component does not define colors or persist preferences. Without the option, only the icon animates. Unsupported browsers, reduced motion, hidden documents, and failed captures switch immediately. Resize or enabling reduced motion settles an active wipe; repeated activation keeps the latest choice. An iframe only animates its own document. Deferred provider updates and router transitions need app coordination; use pageTransition="none" until integrated. With a strict style CSP, pass an allowed nonce. The demo starts with the wipe enabled. Use the With wipe and Without wipe tabs to compare. On the website, the demo changes the entire site theme and stays synchronized with the header.
"use client";
import { useState } from "react";
import { ThemeToggle } from "@/components/theme-toggle";
// Your CSS must define light/dark colors for this attribute.
export function AppearanceToggle() {
const [theme, setTheme] = useState<"light" | "dark">("light");
return (
<ThemeToggle
theme={theme}
pageTransition="circular"
onThemeChange={(next) => {
document.documentElement.dataset.theme = next;
setTheme(next);
}}
/>
);
}Props
| Prop | Type | Description |
|---|---|---|
theme | "light" | "dark" | Controlled appearance; pair with onThemeChange. |
defaultTheme | "light" | "dark" | Initial uncontrolled appearance. Defaults to light. |
onThemeChange | (theme: "light" | "dark") => void | Receives the next appearance when activated. The host owns document theme and persistence. |
pageTransition | "none" | "circular" | Defaults to none. Circular opts into a 620ms document reveal and keeps button scale stable. onThemeChange must apply theme styles synchronously; reduced motion or unavailable transitions switch immediately. |
size | number | Button diameter in pixels. Defaults to 48, with a minimum 44px hit area. |
disabled | boolean | Disables pointer and keyboard activation. |
className / style | string / CSSProperties | Customize the button surface and dimensions. |
aria-label | string | Override the default destination label. Native button props are forwarded. |
nonce | string | Optional CSP nonce for the temporary transition stylesheet. Use your server-generated allowed style nonce. |