Skip to Content

Toggle

An accessible switch/toggle with sm, md, and lg sizes. Supports controlled and uncontrolled modes.

Design Language Comparison

Neobrutalism
Shadcn-inspired
Flowbite-inspired
Glassmorphism
Material Design 3
Neumorphism

Neobrutalism

Hard border, square track, yellow active state.

const TRACK_SIZE = { sm: "w-9 h-[18px]", md: "w-11 h-[22px]", lg: "w-14 h-7" };
const THUMB_SIZE = { sm: "w-3 h-3", md: "w-[15px] h-[15px]", lg: "w-5 h-5" };
const THUMB_CHECKED = { sm: "translate-x-[18px]", md: "translate-x-[22px]", lg: "translate-x-7" };

const NeobrutalismToggle = ({ checked, defaultChecked = false, onChange, disabled = false, label, size = "md", id }) => {
  const [isChecked, setIsChecked] = useState(checked ?? defaultChecked);
  const toggleId = id ?? (label ? label.toLowerCase().replace(/\s+/g, "-") + "-toggle" : "nb-toggle");

  return (
    <label
      htmlFor={toggleId}
      className={`inline-flex items-center gap-2 font-sans cursor-pointer${disabled ? " cursor-not-allowed opacity-60" : ""}`}
    >
      <span className="relative inline-block flex-shrink-0">
        <input
          id={toggleId}
          type="checkbox"
          role="switch"
          checked={isChecked}
          disabled={disabled}
          onChange={(e) => { setIsChecked(e.target.checked); onChange?.(e.target.checked); }}
          className="absolute opacity-0 w-0 h-0"
        />
        <span className={`block border-[3px] border-black rounded-[2px] shadow-[4px_4px_0_#000] relative ${isChecked ? "bg-yellow-400" : "bg-gray-300"} ${TRACK_SIZE[size]}`}>
          <span className={`absolute top-[2px] left-[2px] bg-black border-[2px] border-black rounded-[1px] ${isChecked ? THUMB_CHECKED[size] : ""} ${THUMB_SIZE[size]}`} />
        </span>
      </span>
      {label && <span className="text-[0.9375rem] font-extrabold text-black select-none">{label}</span>}
    </label>
  );
};

Shadcn-inspired

Pill track, zinc active state, smooth transition.

const TRACK_SIZE = { sm: "w-9 h-5", md: "w-11 h-6", lg: "w-14 h-[30px]" };
const THUMB_SIZE = { sm: "w-[14px] h-[14px]", md: "w-[18px] h-[18px]", lg: "w-6 h-6" };
const THUMB_CHECKED = { sm: "translate-x-4", md: "translate-x-5", lg: "translate-x-[26px]" };

const ShadcnToggle = ({ checked, defaultChecked = false, onChange, disabled = false, label, size = "md", id }) => {
  const [isChecked, setIsChecked] = useState(checked ?? defaultChecked);
  const toggleId = id ?? (label ? label.toLowerCase().replace(/\s+/g, "-") + "-toggle" : "shadcn-toggle");

  return (
    <label
      htmlFor={toggleId}
      className={`inline-flex items-center gap-2 font-sans cursor-pointer${disabled ? " cursor-not-allowed opacity-50" : ""}`}
    >
      <span className="relative inline-block flex-shrink-0">
        <input
          id={toggleId}
          type="checkbox"
          role="switch"
          checked={isChecked}
          disabled={disabled}
          onChange={(e) => { setIsChecked(e.target.checked); onChange?.(e.target.checked); }}
          className="absolute opacity-0 w-0 h-0"
        />
        <span className={`block rounded-full border relative transition-colors duration-150 ${isChecked ? "bg-zinc-900 border-zinc-900" : "bg-zinc-200 border-zinc-200"} ${TRACK_SIZE[size]}`}>
          <span className={`absolute top-[2px] left-[2px] bg-white rounded-full shadow-sm transition-transform duration-150 ${isChecked ? THUMB_CHECKED[size] : ""} ${THUMB_SIZE[size]}`} />
        </span>
      </span>
      {label && <span className="text-sm font-medium text-zinc-900 select-none">{label}</span>}
    </label>
  );
};

Flowbite-inspired

Pill track, blue active state, smooth transition.

const TRACK_SIZE = { sm: "w-9 h-5", md: "w-11 h-6", lg: "w-14 h-[30px]" };
const THUMB_SIZE = { sm: "w-[14px] h-[14px]", md: "w-[18px] h-[18px]", lg: "w-6 h-6" };
const THUMB_CHECKED = { sm: "translate-x-4", md: "translate-x-5", lg: "translate-x-[26px]" };

const FlowbiteToggle = ({ checked, defaultChecked = false, onChange, disabled = false, label, size = "md", id }) => {
  const [isChecked, setIsChecked] = useState(checked ?? defaultChecked);
  const toggleId = id ?? (label ? label.toLowerCase().replace(/\s+/g, "-") + "-toggle" : "fb-toggle");

  return (
    <label
      htmlFor={toggleId}
      className={`inline-flex items-center gap-2 font-sans cursor-pointer${disabled ? " cursor-not-allowed opacity-65" : ""}`}
    >
      <span className="relative inline-block flex-shrink-0">
        <input
          id={toggleId}
          type="checkbox"
          role="switch"
          checked={isChecked}
          disabled={disabled}
          onChange={(e) => { setIsChecked(e.target.checked); onChange?.(e.target.checked); }}
          className="absolute opacity-0 w-0 h-0"
        />
        <span className={`block rounded-full border relative transition-all duration-200 ${isChecked ? "bg-[#1c64f2] border-[#1c64f2]" : "bg-gray-200 border-gray-200"} ${TRACK_SIZE[size]}`}>
          <span className={`absolute top-[2px] left-[2px] bg-white rounded-full shadow-sm transition-transform duration-200 ${isChecked ? THUMB_CHECKED[size] : ""} ${THUMB_SIZE[size]}`} />
        </span>
      </span>
      {label && <span className="text-sm font-medium text-gray-900 select-none">{label}</span>}
    </label>
  );
};

Glassmorphism

Translucent track with frosted thumb on gradient background.

const TRACK_SIZE = { sm: "w-9 h-[18px]", md: "w-11 h-[22px]", lg: "w-14 h-7" };
const THUMB_SIZE = { sm: "w-3 h-3", md: "w-[15px] h-[15px]", lg: "w-5 h-5" };
const THUMB_CHECKED = { sm: "translate-x-[18px]", md: "translate-x-[22px]", lg: "translate-x-7" };

const GlassmorphismToggle = ({ checked, defaultChecked = false, onChange, disabled = false, label, size = "md", id }) => {
  const [isChecked, setIsChecked] = useState(checked ?? defaultChecked);
  const toggleId = id ?? (label ? label.toLowerCase().replace(/\s+/g, "-") + "-toggle" : "glass-toggle");

  return (
    <label htmlFor={toggleId} className={`inline-flex items-center gap-2 font-sans cursor-pointer${disabled ? " cursor-not-allowed opacity-40" : ""}`}>
      <span className="relative inline-block flex-shrink-0">
        <input id={toggleId} type="checkbox" role="switch" checked={isChecked} disabled={disabled}
          onChange={(e) => { setIsChecked(e.target.checked); onChange?.(e.target.checked); }}
          className="absolute opacity-0 w-0 h-0" />
        <span className={`block backdrop-blur-md border border-white/30 rounded-full relative transition-all duration-200 ${isChecked ? "bg-white/40" : "bg-white/10"} ${TRACK_SIZE[size]}`}>
          <span className={`absolute top-[2px] left-[2px] bg-white rounded-full shadow-[0_2px_8px_rgba(31,38,135,0.2)] transition-transform duration-200 ${isChecked ? THUMB_CHECKED[size] : ""} ${THUMB_SIZE[size]}`} />
        </span>
      </span>
      {label && <span className="text-[0.9375rem] font-medium text-white select-none">{label}</span>}
    </label>
  );
};

Material Design 3

Rounded track with filled thumb, purple checked state.

const TRACK_SIZE = { sm: "w-9 h-[18px]", md: "w-11 h-[22px]", lg: "w-14 h-7" };
const THUMB_SIZE = { sm: "w-3 h-3", md: "w-[15px] h-[15px]", lg: "w-5 h-5" };
const THUMB_CHECKED = { sm: "translate-x-[18px]", md: "translate-x-[22px]", lg: "translate-x-7" };

const Md3Toggle = ({ checked, defaultChecked = false, onChange, disabled = false, label, size = "md", id }) => {
  const [isChecked, setIsChecked] = useState(checked ?? defaultChecked);
  const toggleId = id ?? (label ? label.toLowerCase().replace(/\s+/g, "-") + "-toggle" : "md3-toggle");

  return (
    <label htmlFor={toggleId} className={`inline-flex items-center gap-2 font-sans cursor-pointer${disabled ? " cursor-not-allowed opacity-40" : ""}`}>
      <span className="relative inline-block flex-shrink-0">
        <input id={toggleId} type="checkbox" role="switch" checked={isChecked} disabled={disabled}
          onChange={(e) => { setIsChecked(e.target.checked); onChange?.(e.target.checked); }}
          className="absolute opacity-0 w-0 h-0" />
        <span className={`block rounded-full relative transition-all duration-200 border-2 ${isChecked ? "bg-[#6750a4] border-[#6750a4]" : "bg-[#e7e0ec] border-[#79747e]"} ${TRACK_SIZE[size]}`}>
          <span className={`absolute top-[2px] left-[2px] rounded-full transition-all duration-200 ${isChecked ? "bg-white " + THUMB_CHECKED[size] : "bg-[#79747e]"} ${THUMB_SIZE[size]}`} />
        </span>
      </span>
      {label && <span className="text-[0.9375rem] font-medium text-[#1c1b1f] select-none">{label}</span>}
    </label>
  );
};

Neumorphism

Concave track, convex thumb — depth achieved through dual shadows.

const TRACK_SIZE = { sm: "w-9 h-[18px]", md: "w-11 h-[22px]", lg: "w-14 h-7" };
const THUMB_SIZE = { sm: "w-3 h-3", md: "w-[15px] h-[15px]", lg: "w-5 h-5" };
const THUMB_CHECKED = { sm: "translate-x-[18px]", md: "translate-x-[22px]", lg: "translate-x-7" };

const NmToggle = ({ checked, defaultChecked = false, onChange, disabled = false, label, size = "md", id }) => {
  const [isChecked, setIsChecked] = useState(checked ?? defaultChecked);
  const toggleId = id ?? (label ? label.toLowerCase().replace(/\s+/g, "-") + "-toggle" : "nm-toggle");

  return (
    <label htmlFor={toggleId} className={`inline-flex items-center gap-2 font-sans cursor-pointer${disabled ? " cursor-not-allowed opacity-50" : ""}`}>
      <span className="relative inline-block flex-shrink-0">
        <input id={toggleId} type="checkbox" role="switch" checked={isChecked} disabled={disabled}
          onChange={(e) => { setIsChecked(e.target.checked); onChange?.(e.target.checked); }}
          className="absolute opacity-0 w-0 h-0" />
        <span className={`block bg-[#e0e5ec] rounded-full relative transition-colors duration-200 shadow-[inset_-3px_-3px_6px_#ffffff,_inset_3px_3px_6px_rgba(163,177,198,0.5)] ${TRACK_SIZE[size]}`}>
          <span className={`absolute top-[2px] left-[2px] rounded-full transition-all duration-200 shadow-[-2px_-2px_4px_#ffffff,_2px_2px_4px_rgba(163,177,198,0.5)] ${isChecked ? "bg-[#6c7a9c] " + THUMB_CHECKED[size] : "bg-[#e0e5ec]"} ${THUMB_SIZE[size]}`} />
        </span>
      </span>
      {label && <span className="text-[0.9375rem] font-semibold text-[#6c7a9c] select-none">{label}</span>}
    </label>
  );
};
Last updated on

© 2026 UI Variants. Built with Nextra.