Switch

Tactile toggle switch with haptic-feel spring bounce, icon slots, and keyboard accessibility.

ReactTailwind CSSFramer Motion
Dependenciesframer-motion, lucide-react
npm install framer-motion lucide-react

Preview

Hardware AccelerationUtilize discrete GPU rendering passes for fluid animations.

Source Code

switch.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
"use client";

import React, { useState } from "react";
import { motion } from "framer-motion";
import { Check } from "lucide-react";

export interface SwitchProps {
  checked?: boolean;
  defaultChecked?: boolean;
  onCheckedChange?: (checked: boolean) => void;
  label?: string;
  description?: string;
  disabled?: boolean;
}

export function Switch({
  defaultChecked = false,
  checked: controlledChecked,
  onCheckedChange,
  label,
  description,
  disabled = false,
}: SwitchProps) {
  const [internalChecked, setInternalChecked] = useState(defaultChecked);
  const isChecked = controlledChecked !== undefined ? controlledChecked : internalChecked;

  const toggle = () => {
    if (disabled) return;
    const next = !isChecked;
    setInternalChecked(next);
    onCheckedChange?.(next);
  };

  return (
    <div className="flex items-center justify-between gap-4 max-w-sm">
      {label && <span className="text-xs font-semibold">{label}</span>}
      <button
        type="button"
        role="switch"
        aria-checked={isChecked}
        onClick={toggle}
        className={`relative inline-flex h-6 w-11 shrink-0 rounded-full p-0.5 transition-colors ${
          isChecked ? "bg-zinc-900" : "bg-zinc-200"
        }`}
      >
        <motion.span
          layout
          className={`h-5 w-5 rounded-full bg-white shadow-sm ${
            isChecked ? "translate-x-5" : "translate-x-0"
          }`}
        />
      </button>
    </div>
  );
}

Usage

usage.tsx
1
2
3
4
5
<Switch
  label="Hardware Acceleration"
  description="Enable GPU rasterization for smooth animation."
  onCheckedChange={(checked) => console.log(checked)}
/>

Props

PropTypeRequiredDefaultDescription
checkedbooleanOptionalundefinedControlled checked state.
defaultCheckedbooleanOptionalfalseUncontrolled initial checked state.
labelstringOptionalundefinedPrimary label displayed alongside the switch.
onCheckedChange(checked: boolean) => voidOptionalundefinedCallback triggered when the switch value changes.

Types

types.ts
1
2
3
4
5
6
7
8
export interface SwitchProps {
  checked?: boolean;
  defaultChecked?: boolean;
  onCheckedChange?: (checked: boolean) => void;
  label?: string;
  description?: string;
  disabled?: boolean;
}