Icon button that switches theme with a full-page reveal animation, powered by the View Transitions API.
Use ThemeTogglerButton anywhere you need a light/dark (or light/dark/system) switch that feels like more than a plain icon swap. When the browser supports the View Transitions API, clicking the button plays a full-page reveal animation seeded at the button's own position; browsers without support (and users with reduced motion enabled) fall back to an instant theme change, so the component is safe to use everywhere.
pnpm dlx shadcn@latest add https://matos-ui.com/r/theme-toggler-button.jsonimport { ThemeTogglerButton } from "@/components/matos-ui/theme-toggler-button"
<ThemeTogglerButton />Requires a ThemeProvider from next-themes (with attribute="class") somewhere above it in the tree.
variant controls the reveal animation played on the incoming theme:
Seeded at the button
circle — a circle expands outward from the button, revealing the new theme (default).circle-blur — the same circle reveal, softened with a blur-to-sharp transition.iris — a circle reveal with a spring overshoot, like a camera iris snapping into focus.rectangle — a rectangle unfolds from the click point, every edge racing to the matching edge of the screen.zoom — the new theme scales up from the button and fades in over the old one.Directional sweeps (obey direction)
polygon — a slanted wipe whose angle shifts mid-flight, rejoining flush at both ends.diagonal — a wipe that holds one constant 45°-ish angle for its whole travel.slide — a straight curtain wipe, axis-aligned to direction.blinds — six venetian slats grow together, the gaps between them closing to nothing.Fallback
fade — a plain crossfade, also used automatically when the View Transitions API is unavailable.<ThemeTogglerButton variant="iris" />
<ThemeTogglerButton variant="blinds" direction="rtl" />
<ThemeTogglerButton variant="diagonal" direction="ttb" />direction ("ltr" | "rtl" | "ttb" | "btt") sets the sweep axis for polygon, diagonal, slide and blinds, and the rotation direction of the icon swap. It has no visible effect on the button-seeded variants (circle, circle-blur, iris, rectangle, zoom) or on fade.
Pass modes to cycle through light, dark, and system instead of just light/dark. The icon switches to a monitor glyph while system is active.
<ThemeTogglerButton modes={["light", "dark", "system"]} />Override any of the icons per mode:
import { CloudMoon, CloudSun } from "lucide-react"
<ThemeTogglerButton
icons={{ light: <CloudSun />, dark: <CloudMoon /> }}
/>| Prop | Type | Default |
|---|---|---|
variant | "circle" | "circle-blur" | "iris" | "polygon" | "slide" | "fade" | "rectangle" | "diagonal" | "blinds" | "zoom" | "circle" |
size | "sm" | "md" | "lg" | "icon" | "md" |
direction | "ltr" | "rtl" | "ttb" | "btt" | "ltr" |
modes | ("light" | "dark" | "system")[] | ["light", "dark"] |
duration | number | 650 |
icons | Partial<Record<"light" | "dark" | "system", ReactNode>> | - |
For icon-only buttons the component already sets an aria-label describing the next theme; pass your own aria-label to override it.
Also exported: themeTogglerButtonVariants, ThemeTogglerButtonVariant, ThemeTogglerButtonDirection, ThemeTogglerButtonMode, and ThemeTogglerButtonProps.
Install Matos UI
Choose a package manager and copy one command for every component.