← Back
UI

Toggle Switch

Three toggle variants (pill, iOS-style, square) with smooth CSS transitions.

ToggleSwitch.tsx74 lines
"use client";

import { useState } from "react";

function Toggle({
  label,
  sub,
  defaultOn = false,
  variant = "pill",
}: {
  label: string;
  sub?: string;
  defaultOn?: boolean;
  variant?: "pill" | "ios" | "square";
}) {
  const [on, setOn] = useState(defaultOn);

  const trackCls =
    variant === "ios"
      ? `w-14 h-8 rounded-full ${on ? "bg-emerald-500" : "bg-zinc-300 dark:bg-zinc-600"}`
      : variant === "square"
      ? `w-12 h-6 rounded-lg ${on ? "bg-violet-500" : "bg-zinc-300 dark:bg-zinc-700"}`
      : `w-12 h-6 rounded-full ${on ? "bg-violet-500" : "bg-zinc-300 dark:bg-zinc-700"}`;

  const thumbCls =
    variant === "ios"
      ? `w-6 h-6 rounded-full bg-white shadow-md`
      : variant === "square"
      ? `w-4 h-4 rounded-md bg-white shadow`
      : `w-5 h-5 rounded-full bg-white shadow`;

  const thumbLeft = on
    ? variant === "ios"
      ? "calc(100% - 28px)"
      : variant === "square"
      ? "calc(100% - 20px)"
      : "calc(100% - 23px)"
    : "3px";

  return (
    <button
      onClick={() => setOn((v) => !v)}
      className="flex items-center justify-between w-full py-3 border-b border-zinc-100 dark:border-zinc-800 last:border-0 group"
    >
      <div className="text-left">
        <p className="text-sm font-medium text-zinc-800 dark:text-zinc-200">{label}</p>
        {sub && <p className="text-xs text-zinc-400 dark:text-zinc-600">{sub}</p>}
      </div>
      <div
        className={`relative shrink-0 transition-colors duration-250 ${trackCls}`}
        role="switch"
        aria-checked={on}
      >
        <span
          className={`absolute top-1/2 -translate-y-1/2 transition-all duration-250 ${thumbCls}`}
          style={{ left: thumbLeft }}
        />
      </div>
    </button>
  );
}

export default function ToggleSwitch() {
  return (
    <div className="w-full max-w-sm mx-auto py-4 px-2">
      <Toggle label="Dark mode"        sub="Switch to dark theme"       defaultOn variant="pill"   />
      <Toggle label="Notifications"    sub="Receive push alerts"        defaultOn variant="ios"    />
      <Toggle label="Analytics"        sub="Send anonymous usage data"            variant="square" />
      <Toggle label="Auto-save"        sub="Save every 30 seconds"      defaultOn variant="pill"   />
      <Toggle label="Beta features"    sub="Early access to new tools"            variant="ios"    />
    </div>
  );
}

More in UI