Theme Toggle Effect

Animated transitions when switching between light and dark themes.

Browser compatibility

This component uses the View Transitions API. Check the latest browser compatibility on MDN before using it in production. Browsers without the API, and users who prefer reduced motion, switch themes instantly.

Installation

Pick one effect. Each command installs the helper and that effect's CSS.

Triangle

Cortexcn
pnpm dlx shadcn@latest add @cortexcn/theme-toggle-effect-triangle

Triangle blur

Cortexcn
pnpm dlx shadcn@latest add @cortexcn/theme-toggle-effect-triangle-blur

Circle

Cortexcn
pnpm dlx shadcn@latest add @cortexcn/theme-toggle-effect-circle

Circle blur

Cortexcn
pnpm dlx shadcn@latest add @cortexcn/theme-toggle-effect-circle-blur

Circle blur (top left)

Cortexcn
pnpm dlx shadcn@latest add @cortexcn/theme-toggle-effect-circle-blur-top-left

Polygon

Cortexcn
pnpm dlx shadcn@latest add @cortexcn/theme-toggle-effect-polygon

Polygon gradient

Cortexcn
pnpm dlx shadcn@latest add @cortexcn/theme-toggle-effect-polygon-gradient

Usage

Wrap your theme setter with startThemeTransition and pass the effect you installed:

"use client";

import { useTheme } from "next-themes";
import { startThemeTransition } from "@/components/theme-toggle-effect";

export function ThemeToggle() {
  const { resolvedTheme, setTheme } = useTheme();

  function toggleTheme() {
    const next = resolvedTheme === "dark" ? "light" : "dark";
    startThemeTransition(() => setTheme(next), "circle");
  }

  return <button onClick={toggleTheme}>Toggle theme</button>;
}

It works with any theme setter, not only next-themes:

startThemeTransition(() => {
  document.documentElement.classList.toggle("dark");
}, "polygon");

The preview above has every effect installed so you can compare them. In your app, install the effect you use and pass the same name to startThemeTransition.

startThemeTransition sets data-theme-toggle-effect on <html> for the length of the switch, and every effect rule is scoped to that attribute. The effect CSS never runs for other view transitions, such as page navigations.

API

function startThemeTransition(
  update: () => void,
  effect?: ThemeToggleEffect,
): void;
ParameterTypeDefaultDescription
update() => voidrequiredApplies the new theme, usually setTheme
effectThemeToggleEffect"circle"Which reveal effect to play

THEME_TOGGLE_EFFECTS is a readonly array of every effect name, and ThemeToggleEffect is the union of its values.

References