Inputs

Signature Pad

Handwritten signatures on canvas: pressure-sensitive stroke width, clear and redraw, export to PNG.

A handwritten signature pad built with canvas 2d and Pointer Events: the line width changes live with writing speed (or with a stylus's real pressure) to imitate a fountain pen. Strokes are kept on a stack, supporting stroke-by-stroke undo, one-click clear, and PNG download, plus a dashed signature baseline and a "please sign" watermark.

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add signature-pad.en

New project or installation trouble? Start here →

Signature Pad is a React signature capture component built with canvas 2D and Motion. Drop it into a form to collect an e-signature: the result comes back as a PNG dataURL through onChange, and users can also download it as an image file.

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

1.5
4
220
Active edited usageDefault props
<SignaturePad/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

When to use

  • The signing step of an online contract, authorization, or consent form
  • Proof-of-delivery screens in delivery and logistics apps
  • Membership or check-in forms for clinics, gyms, and rental services
  • On-site sign-in and acceptance sheets on a tablet
  • Not a good fit: flows where the user only needs to agree and you don't need to keep an image of the signature; a regular checkbox is enough

Installation

npx webberui@latest add https://webberui.com/r/signature-pad.en.json

Or, once registries are configured in components.json, install it as @webberui/signature-pad.

Usage

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

<SignaturePad
  height={200}
  onChange={(dataUrl) => {
    // dataUrl is a PNG dataURL, or null when the canvas is empty
    console.log(dataUrl);
  }}
/>

Examples

Store it in form state and check on submit

onChange passes null when the canvas is empty, so you can use it directly to tell whether the user has signed and to gate the submit button.

"use client";

import * as React from "react";
import { SignaturePad } from "@/components/ui/signature-pad";

export function ConsentForm() {
  const [signature, setSignature] = React.useState<string | null>(null);

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        // signature is a PNG dataURL
      }}
    >
      <SignaturePad height={180} onChange={setSignature} />
      <button type="submit" disabled={!signature}>
        Submit consent form
      </button>
    </form>
  );
}

Upload to an API

Turn the dataURL into a Blob with fetch, then send it to your backend with FormData.

async function uploadSignature(dataUrl: string) {
  const blob = await fetch(dataUrl).then((res) => res.blob());
  const body = new FormData();
  body.append("signature", blob, "signature.png");
  await fetch("/api/signatures", { method: "POST", body });
}

<SignaturePad
  onChange={(dataUrl) => {
    if (dataUrl) void uploadSignature(dataUrl);
  }}
/>

Transparent background and custom ink

When the signature will be layered onto a PDF or another document, export a transparent PNG with exportBackground="transparent" and set blue ink with strokeColor.

<SignaturePad
  strokeColor="#1d4ed8"
  minWidth={1}
  maxWidth={3}
  exportBackground="transparent"
  fileName="contract-signature"
  placeholder="Sign here to accept"
/>

Props

PropTypeDefaultDescription
strokeColorstring"currentColor"Ink colour; by default it reads the container's text colour at the moment the stroke starts, adapting to light and dark themes automatically
minWidthnumber1.5Thinnest line width during a fast stroke (px)
maxWidthnumber4Thickest line width during a slow stroke (px)
heightnumber220Height of the signature area (px); the width fills the container automatically
placeholderstring"Sign here"Watermark text shown before any stroke is drawn
fileNamestring"signature"File name for the PNG download (without the extension)
exportBackgroundstring"#ffffff"Background colour of the exported PNG; set "transparent" to output a transparent background
onChange(dataUrl: string | null) => void—Callback after every stroke ends, undo, or clear; passes null when the canvas is empty
classNamestring—Forwarded to the outermost container

Accessibility

  • The signature area carries role="img" and aria-label="Handwritten Signature Area", and the toolbar buttons are native buttons with text labels and a disabled state
  • It uses Pointer Events with touch-action: none, so mouse, finger, and stylus all write; a stylus uses its real pressure values
  • When the user has "reduce motion" enabled at the system level, the watermark transition and the button micro-scale animation are disabled, while the writing functionality is completely unaffected
  • A handwritten signature inherently requires a pointing device; in a production form, please also offer an alternative way to consent (for example a checkbox plus a typed name)

FAQ

Does it work with the Next.js App Router?

Yes. The file already starts with "use client", so you can import it from a Server Component page as usual.

How do I get the signature image?

After every finished stroke, undo, or clear, onChange receives a PNG dataURL, or null when the canvas is empty. Users can also press "Download PNG" in the toolbar; the file name comes from fileName.

Is the exported image sharp enough?

The canvas sizes its pixel buffer by devicePixelRatio, and when the container resizes it reallocates the buffer and redraws every stroke, so exported PNGs stay sharp on high-density screens.

Is stylus pressure supported?

Yes. When pointerType is pen and a pressure value is present, the line width follows the real pressure between minWidth and maxWidth. Mouse and finger input use writing speed instead: the faster the stroke, the thinner the line.

What are the dependencies?

motion, lucide-react, clsx, and tailwind-merge. If you'd rather skip the CLI, the Manual tab in the installation section walks you through copying the source.