Skip to content
All Taste components

Micro-interactions

Sun and moon toggle

A light and dark switch whose sun folds its rays away and turns into a crescent moon.

switch · icon button · light and dark

SwitchIcon button
"use client";

import { useState } from "react";
import { cn } from "@/lib/utils";

function SunMoon({ dark }: { dark: boolean }) {
  const spring = "transition duration-500 ease-spring motion-reduce:transition-none";
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" aria-hidden className="size-[18px] overflow-visible">
      <g className={cn(spring, "origin-center [transform-box:view-box]", dark && "rotate-90 scale-[.4] opacity-0")}>
        <path d="M12 2.6v2M12 19.4v2M2.6 12h2M19.4 12h2M17.2 6.8l1.4-1.4M6.8 6.8 5.4 5.4M17.2 17.2l1.4 1.4M6.8 17.2l-1.4 1.4" />
      </g>
      <circle cx="12" cy="12" r="7" fill="currentColor" stroke="none" className={cn(spring, "origin-center [transform-box:fill-box]", dark ? "scale-100" : "scale-[.6]")} />
      <circle cx="17.5" cy="6.5" r="5.5" stroke="none" className={cn(spring, "fill-bg origin-center [transform-box:fill-box]", dark ? "opacity-100" : "translate-x-[7px] -translate-y-[7px] scale-[.4] opacity-0")} />
    </svg>
  );
}

export function ThemeSwitch({ dark, onChange }: { dark: boolean; onChange: (dark: boolean) => void }) {
  return (
    <button
      type="button"
      role="switch"
      aria-checked={dark}
      aria-label="Dark mode"
      onClick={() => onChange(!dark)}
      className={cn(
        "relative h-[34px] w-16 rounded-full transition-colors duration-500 motion-reduce:transition-none",
        "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
        dark ? "bg-primary-soft" : "bg-warning-soft",
      )}
    >
      <span
        className={cn(
          "absolute left-[3px] top-[3px] grid size-7 place-items-center rounded-full bg-bg shadow transition duration-500 ease-spring motion-reduce:transition-none",
          dark ? "translate-x-[30px] text-primary" : "text-warning",
        )}
      >
        <SunMoon dark={dark} />
      </span>
    </button>
  );
}

export function ThemeToggleDemo() {
  const [dark, setDark] = useState(false);
  return <ThemeSwitch dark={dark} onChange={setDark} />;
}

The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.