Skip to content
usefy

SignaturePad

ComponentsSignature capture

Electronic signature pad for React — hand-written ink engine, velocity/pressure-based variable stroke width, PNG/SVG/JSON exports, zero dependencies

Live demo

Install

$ npm install @usefy/signature-pad

Quick start

SignaturePad.tsx
import { useRef, useState } from "react";
import { SignaturePad, type SignaturePadController } from "@usefy/signature-pad";

function ConsentForm() {
  const pad = useRef<SignaturePadController>(null);
  const [signed, setSigned] = useState(false);

  return (
    <>
      <div style={{ height: 200, border: "1px solid #e2e8f0", borderRadius: 12 }}>
        <SignaturePad
          guideline
          controllerRef={pad}
          onChange={({ isEmpty }) => setSigned(!isEmpty)}
        />
      </div>
      <button
        disabled={!signed}
        onClick={async () => {
          const { blob } = await pad.current!.toPNG({ background: "#fff" });
          await upload(blob);
        }}
      >
        Agree &amp; sign
      </button>
    </>
  );
}

API reference

<SignaturePad />

<SignaturePad
  penColor="#1e293b"
  guideline={{ y: 140, inset: 24, color: "#94a3b8" }}
  defaultValue={storedSignature}   // applied once on mount
  readOnly={false}                 // reactive — input detaches in place
  controllerRef={pad}              // imperative controller (stable)
  onBegin={() => {}}
  onEnd={() => {}}
  onChange={({ isEmpty, strokeCount }) => {}} // edges only, never per point
  aria-label="Signature input area"
  className="..."
  style={{ ... }}
/>
  • controllerRef receives a stable SignaturePadController: clear() / undo() / redo() / isEmpty() / toPNG() / toSVG() / toJSON() / fromJSON(). Calls before mount (or after unmount) are safe no-ops.
  • guideline draws a "sign here" baseline beneath the ink, on-canvas — and it is excluded from every export by construction.
  • Ink props are live: changing penColor (etc.) never wipes the pad — committed strokes keep the style they were drawn with; new strokes pick up the new options. (History resets, and a stroke in progress at that exact moment is discarded.)

useSignaturePad()

import { useSignaturePad } from "@usefy/signature-pad";

function SignatureField() {
  const pad = useSignaturePad({ penColor: "#0f172a" });
  return (
    <>
      <canvas ref={pad.canvasRef} style={{ width: "100%", height: 200 }} />
      <button onClick={pad.undo} disabled={!pad.canUndo}>Undo</button>
      <button onClick={pad.redo} disabled={!pad.canRedo}>Redo</button>
      <button onClick={pad.clear} disabled={pad.isEmpty}>Clear</button>
    </>
  );
}

Returns { canvasRef, isEmpty, strokeCount, canUndo, canRedo, clear, undo, redo, toPNG, toSVG, toJSON, fromJSON } — every function referentially stable. The engine is created when the canvas attaches and destroyed when it detaches; options are read once at attach (remount or attach a new canvas to change them).


Go deeper

This page is the quick reference. For every example, prop, and edge case, read the full README — or open Storybook to change props live.

More in components