Switch

Switch with Blob Animation

Installation

Install Dependencies

npm i clsx tailwind-merge framer-motion

Create @/utils/cn.ts file

import clsx, { ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

Switch Component

"use client";
import { cn } from "@/lib/utils";

interface SwitchProps {
  size?: "small" | "medium" | "large" | string;
  checked?: boolean;
  onChange?: (checked: boolean) => void;
  className?: string;
}

export default function Switch({ size = "small", checked, onChange, className }: SwitchProps) {
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    if (onChange) {
      onChange(e.target.checked);
    }
  };
  let sizeValue: string;

  switch (size) {
    case "small":
      sizeValue = "20px";
      break;
    case "medium":
      sizeValue = "30px";
      break;
    case "large":
      sizeValue = "40px";
      break;
    default:
      sizeValue = size;
      break;
  }
  return (
    <div>
      <input
        type="checkbox"
        checked={checked}
        onChange={handleChange}
        className={cn(
          `grid aspect-[2.25] w-auto cursor-pointer appearance-none overflow-hidden bg-gray-300 checked:bg-orange-500`,
          className
        )}
        style={
          {
            "--s": sizeValue,
            "height": `calc(var(--s) + var(--s) / 5)`,
            "borderRadius": `var(--s)`,
            "margin": `calc(var(--s) / 2)`,
            "transition": ".3s .1s"
          } as React.CSSProperties
        }
      />
      <style jsx>
        {`
          input:before {
            content: "";
            padding: calc(var(--s) / 10);
            --_g: radial-gradient(circle closest-side at calc(100% - var(--s) / 2) 50%, #000 96%, #0000);
            background:
              var(--_g) 0 / var(--_p, var(--s)) 100% no-repeat content-box,
              var(--_g) var(--_p, 0) / var(--s) 100% no-repeat content-box,
              #fff;
            mix-blend-mode: darken;
            filter: blur(calc(var(--s) / 12)) contrast(11);
            transition:
              0.4s,
              background-position 0.4s 0.1s,
              padding cubic-bezier(0, calc(var(--_i, -1) * 10), 1, calc(var(--_i, -1) * 200)) 0.25s 0.1s;
          }

          input:checked:before {
            padding: calc(var(--s) / 10 + 0.05px) calc(var(--s) / 10);
            --_p: 100%;
            --_i: 1;
          }
        `}
      </style>
    </div>
  );
}

Examples

Medium

Large

Custom