ComponentsToggle
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