Theme Toggler Button

PreviousNext

Icon button that switches theme with a full-page reveal animation, powered by the View Transitions API.

Circle
Circle blur
Iris
Rectangle
Zoom
Fade
Polygon
Diagonal
Slide
Blinds

When To Use

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.

Installation

pnpm dlx shadcn@latest add https://matos-ui.com/r/theme-toggler-button.json

Usage

import { ThemeTogglerButton } from "@/components/matos-ui/theme-toggler-button"

<ThemeTogglerButton />

Requires a ThemeProvider from next-themes (with attribute="class") somewhere above it in the tree.

Animation Variants

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.

Three-Way Toggle

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"]} />

Custom Icons

Override any of the icons per mode:

import { CloudMoon, CloudSun } from "lucide-react"

<ThemeTogglerButton
  icons={{ light: <CloudSun />, dark: <CloudMoon /> }}
/>

Reference

Props

PropTypeDefault
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"]
durationnumber650
iconsPartial<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.