Skip to content
motion basics
Back to the collection

STUDY / 003 · Inputs

Spring toggle

A tiny interaction with just enough spring.

Spring physics

Preview

A little brighter.

Live preview · Go ahead, interact with it

Code

"use client";
import { Check, Moon, Sun } from "lucide-react";
import { motion, useReducedMotion } from "motion/react";
import { useState } from "react";
import { cn } from "@/lib/utils";
export default function SpringToggle({ className }: { className?: string }) {
  const [on, setOn] = useState(true);
  const reduced = useReducedMotion();
  return (
    <div className={cn("flex flex-col items-center gap-6", className)}>
      <div className="flex items-center gap-5">
        <Moon
          size={19}
          strokeWidth={1.5}
          className={
            on ? "text-neutral-300" : "text-neutral-700 dark:text-neutral-300"
          }
        />
        <button
          type="button"
          role="switch"
          aria-checked={on}
          aria-label="Enable light mode"
          onClick={() => setOn(!on)}
          className={cn(
            "flex h-12 w-21 items-center rounded-full p-1.5 transition-colors active:scale-95",
            on ? "justify-end bg-[#c6e5a3]" : "justify-start bg-neutral-300",
          )}
        >
          <motion.span
            layout
            transition={
              reduced
                ? { duration: 0 }
                : { type: "spring", stiffness: 500, damping: 28 }
            }
            className="grid size-9 place-items-center rounded-full bg-white dark:bg-[#0a0a0a] shadow-sm"
          >
            <Check
              size={15}
              className={on ? "text-[#698b46]" : "text-neutral-300"}
            />
          </motion.span>
        </button>
        <Sun
          size={20}
          strokeWidth={1.5}
          className={
            on ? "text-neutral-700 dark:text-neutral-300" : "text-neutral-300"
          }
        />
      </div>
      <span className="font-mono text-[10px] text-neutral-400 dark:text-neutral-500">
        {on ? "A little brighter." : "A little quieter."}
      </span>
    </div>
  );
}

React · TypeScript · Tailwind CSS

Copy the component and the cn helper into your project.

bun add motion lucide-react clsx tailwind-merge