Signature Pad

Form

Canvas-based signature capture pad.

Preview

Usage

example.jsx
import { SignaturePad } from "@/components/ui/signature-pad";

export default function Example() {
  return <SignaturePad />;
}

Source Code

Copy this file into components/ui/signature-pad.jsx in your project.

signature-pad.jsx
"use client";

import { forwardRef, useRef, useEffect, useState } from "react";
import { cn } from "@/lib/utils";

const SignaturePad = forwardRef(({ className, onSignature, width = 400, height = 200, ...props }, ref) => {
  const canvasRef = useRef(null);
  const [drawing, setDrawing] = useState(false);

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext("2d");
    ctx.strokeStyle = "currentColor";
    ctx.lineWidth = 2;
    ctx.lineCap = "round";
  }, []);

  const getPos = (e) => {
    const rect = canvasRef.current.getBoundingClientRect();
    const clientX = e.touches ? e.touches[0].clientX : e.clientX;
    const clientY = e.touches ? e.touches[0].clientY : e.clientY;
    return { x: clientX - rect.left, y: clientY - rect.top };
  };

  const start = (e) => {
    setDrawing(true);
    const ctx = canvasRef.current.getContext("2d");
    const pos = getPos(e);
    ctx.beginPath();
    ctx.moveTo(pos.x, pos.y);
  };

  const draw = (e) => {
    if (!drawing) return;
    const ctx = canvasRef.current.getContext("2d");
    const pos = getPos(e);
    ctx.lineTo(pos.x, pos.y);
    ctx.stroke();
  };

  const stop = () => {
    setDrawing(false);
    onSignature?.(canvasRef.current?.toDataURL());
  };

  const clear = () => {
    const canvas = canvasRef.current;
    const ctx = canvas.getContext("2d");
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    onSignature?.("");
  };

  return (
    <div ref={ref} className={cn("inline-flex flex-col gap-2", className)} {...props}>
      <canvas
        ref={canvasRef}
        width={width}
        height={height}
        className="rounded-md border border-input bg-background cursor-crosshair touch-none"
        onMouseDown={start} onMouseMove={draw} onMouseUp={stop} onMouseLeave={stop}
        onTouchStart={start} onTouchMove={draw} onTouchEnd={stop}
      />
      <button type="button" onClick={clear} className="self-end text-xs text-muted-foreground hover:text-foreground cursor-pointer">Clear</button>
    </div>
  );
});
SignaturePad.displayName = "SignaturePad";

export { SignaturePad };

Quick Install

Make sure you have the cn() utility set up. It requires clsx and tailwind-merge.

npm install clsx tailwind-merge