Theme Toggle Button

Button with morphing SVG animation between Sun and Moon icons using GSAP

Getting Started

Install dependencies

npm install next-themes gsap

Add the component

npx shadcn@latest add https://glsilk.vercel.app/r/theme-toggle-button.json

Create a theme provider

components/theme-provider.tsx
"use client";

import * as React from "react";
import { ThemeProvider as NextThemesProvider } from "next-themes";

export function ThemeProvider({
  children,
  ...props
}: React.ComponentProps<typeof NextThemesProvider>) {
  return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
}

Wrap your root layout

app/layout.tsx
import { ThemeProvider } from "@/components/theme-provider";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
          {children}
        </ThemeProvider>
      </body>
    </html>
  );
}

Create a reusable mode toggle component

components/mode-toggle.tsx
"use client";

import { useTheme } from "next-themes";
import { ThemeToggleButton } from "@/components/ui/theme-toggle-button";

export function ModeToggle() {
  const { theme } = useTheme();
  return (
    <div className="flex flex-col items-center gap-4">
      <ThemeToggleButton variant="ghost" size="icon" />
    </div>
  );
}

Examples

Default

Open in
<ThemeToggleButton>Toggle Theme</ThemeToggleButton>

Rounded

Open in
<div className="flex gap-4">
  <ThemeToggleButton variant="outline" size="icon" className="rounded-full" />
  <ThemeToggleButton variant="default" size="icon" className="rounded-full" />
  <ThemeToggleButton variant="destructive" size="icon" className="rounded-full" />
</div>

<div className="flex gap-4">
  <ThemeToggleButton
    variant="outline"
    size="icon"
    className="rounded-full [&_svg_circle]:fill-yellow-500 [&_svg_path]:stroke-yellow-500 [&[data-state=checked]_svg_circle]:fill-blue-500 [&[data-state=checked]_svg_path]:stroke-blue-500"
  />
  <ThemeToggleButton
    variant="outline"
    size="icon"
    className="rounded-full [&_svg_circle]:fill-orange-500 [&_svg_path]:stroke-orange-500 [&[data-state=checked]_svg_circle]:fill-purple-500 [&[data-state=checked]_svg_path]:stroke-purple-500"
  />
  <ThemeToggleButton
    variant="outline"
    size="icon"
    className="rounded-full [&_svg_circle]:fill-green-500 [&_svg_path]:stroke-green-500 [&[data-state=checked]_svg_circle]:fill-pink-500 [&[data-state=checked]_svg_path]:stroke-pink-500"
  />
</div>

Custom Colors

Open in
<ThemeToggleButton
  size="icon"
  variant="outline"
  className="[&_svg_circle]:fill-yellow-500 [&_svg_path]:stroke-yellow-500"
/>

<ThemeToggleButton
  size="icon"
  variant="outline"
  className="[&_svg_circle]:fill-blue-500 [&_svg_path]:stroke-blue-500"
/>

<ThemeToggleButton
  size="icon"
  variant="outline"
  className="[&_svg_circle]:fill-orange-500 [&_svg_path]:stroke-orange-500"
/>

<ThemeToggleButton
  size="icon"
  variant="outline"
  className="[&_svg_circle]:fill-purple-500 [&_svg_path]:stroke-purple-500"
/>

Dual Color Mode

Open in
<ThemeToggleButton
  size="icon"
  variant="outline"
  className="[&_svg_circle]:fill-yellow-500 [&_svg_path]:stroke-yellow-500 [&[data-state=checked]_svg_circle]:fill-blue-500 [&[data-state=checked]_svg_path]:stroke-blue-500"
/>

<ThemeToggleButton
  size="icon"
  variant="outline"
  className="[&_svg_circle]:fill-orange-500 [&_svg_path]:stroke-orange-500 [&[data-state=checked]_svg_circle]:fill-purple-500 [&[data-state=checked]_svg_path]:stroke-purple-500"
/>

<ThemeToggleButton
  size="icon"
  variant="outline"
  className="[&_svg_circle]:fill-green-500 [&_svg_path]:stroke-green-500 [&[data-state=checked]_svg_circle]:fill-pink-500 [&[data-state=checked]_svg_path]:stroke-pink-500"
/>

API Reference

Prop

Type

Credits

glsilk