Toggle Switch

Accessible switch with a springy knob, shown in a settings list.

Email notifications

Get a summary of activity every morning.

Push notifications

Real-time alerts on your devices.

Weekly report

Metrics and trends every Monday.

Installation

  1. Install the dependencies

    $ npm i motion lucide-react
  2. Copy the source into components/ui/toggle-switch.tsx

    components/ui/toggle-switch.tsx
    "use client";
    
    import { motion } from "motion/react";
    import { useState } from "react";
    
    type ToggleSwitchProps = {
      checked: boolean;
      onChange: (checked: boolean) => void;
      label?: string;
    };
    
    export function ToggleSwitch({ checked, onChange, label }: ToggleSwitchProps) {
      return (
        <button
          role="switch"
          aria-checked={checked}
          aria-label={label}
          onClick={() => onChange(!checked)}
          className={`flex h-7 w-12 shrink-0 items-center rounded-full p-1 transition-colors duration-300 focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-blue-600/20 ${
            checked ? "justify-end bg-blue-600" : "justify-start bg-zinc-200"
          }`}
        >
          <motion.span
            layout
            transition={{ type: "spring", stiffness: 600, damping: 32 }}
            className="size-5 rounded-full bg-white shadow-md"
          />
        </button>
      );
    }
    
    const settings = [
      { id: "email", title: "Email notifications", text: "Get a summary of activity every morning." },
      { id: "push", title: "Push notifications", text: "Real-time alerts on your devices." },
      { id: "weekly", title: "Weekly report", text: "Metrics and trends every Monday." },
    ];
    
    export default function Demo() {
      const [state, setState] = useState<Record<string, boolean>>({ email: true, push: false, weekly: true });
      return (
        <div className="w-full max-w-md divide-y divide-zinc-100 rounded-2xl border border-zinc-200 bg-white">
          {settings.map((s) => (
            <div key={s.id} className="flex items-center justify-between gap-6 p-5">
              <div>
                <p className="text-sm font-medium text-zinc-900">{s.title}</p>
                <p className="text-sm text-zinc-500">{s.text}</p>
              </div>
              <ToggleSwitch label={s.title} checked={state[s.id]} onChange={(v) => setState((p) => ({ ...p, [s.id]: v }))} />
            </div>
          ))}
        </div>
      );
    }
    
  3. Use it

    The file exports the component and a Demo showing typical usage. Import whichever you need.