Skip to content

Pressure Signature Pad

A signature pad with real variable-width ink — pen pressure where it exists, velocity everywhere else, coalesced samples and midpoint quadratics.

Canvas 2D0 deps1 file · 9 KB TSX

Prominimaleditorial
Preview

Props

0.7
3.4
0.72

The source is part of Pro

$27/month or $179 once. See Pro

No runtime dependencies
"use client";

import { useCallback, useEffect, useRef, useState } from "react";

type Pt = { x: number; y: number; w: number };

export function PressureSignaturePad({
  minWidth = 0.7,
  maxWidth = 3.4,
  ink = "#f4f6f8",
  velocityWeight = 0.72,
  label = "Signature",
  onChange,
}: {
  minWidth?: number;
  maxWidth?: number;
  ink?: string;
  /** How much of the fallback width comes from speed rather than a flat mid value. */

  // ---------------------------------------------------------------
  // Full source available with a viberdy Pro subscription.
  // https://viberdy.dev/pricing
  // ---------------------------------------------------------------
}
Details

About this component

Nearly every signature pad on the web draws a constant-width line, and the reason is a single API detail: PointerEvent.pressure reports exactly one half while a mouse button is held and zero otherwise. Binding the line width to it therefore produces a completely flat stroke on the device almost everyone builds and tests on, and only comes alive on a pen, which is the hardware nobody checks. The fix is to treat pressure as available rather than assumed — use it when the pointer really reports it, and otherwise derive width from velocity, because a fast stroke of a real nib runs thin and a slow one pools. That single substitution is what makes a mouse signature look handwritten. Three supporting details finish it. The pointer must be read through getCoalescedEvents, since a pen samples far faster than the display refreshes and the browser buffers everything but one sample per frame — miss them and a quick stroke is visibly polygonal. The smoothing is midpoint quadratics, curving between the midpoints of consecutive samples with the sample as the control point, which is continuous in the first derivative without any tangent bookkeeping. And the samples, not the bitmap, are the document: assigning to canvas.width clears it even when the value is unchanged, so a pad that stores only pixels loses the signature on any resize — on a phone, the moment the keyboard opens.

Curator’s note

A mouse reports pressure 0.5 exactly, always. Bind lineWidth to it and the stroke is flat on every machine you test on and alive only on hardware you do not have.

Pairs well with

Pro

OTP Code Input

components