Signature Pad
FreeSign with a finger, a pen or a mouse. The signature is held as STROKES rather than pixels, so it survives a resize, undo removes a mark rather than a point, and export refuses to hand back a blank image from a pad nobody signed.
</>sv-signature-pad signaturecanvasconsent
import { SvSignaturePadComponent } from '@styloviz/forms';Demo & usage
Basic usage
Usage example — open the gallery for the full interactive demo.
An interactive demo for this component lives in the gallery.
Open live demoMore examples
Gate a submit on a real signature
example.htmlAngular
2 lines
<sv-signature-pad label="Sign the agreement" (emptyChange)="unsigned.set($event)" /> <sv-button label="Accept" [disabled]="unsigned()" (clicked)="submit()" />
Export — and handle the empty case
example.htmlAngular
3 lines
const png = pad.toDataUrl(); // null when nothing was signed const svg = pad.toSvgString(); // null too — a signature is a VECTOR if (!png) { /* do not file a blank image as consent */ }
Replay a stored signature at any size
example.htmlAngular
1 lines
pad.setValue(saved); // rescaled into the current box from saved.width/height
When to use
- Consent, delivery confirmation, a contract, a waiver — anywhere a person signs.
- You need the mark itself, not a typed name.
- For a typed acknowledgement use a Checkbox with the terms beside it; a drawn signature that nobody checks is not stronger evidence.
Async states
None — this component has nothing to load, so it has no loading, empty or error state to drive.
Inputs
| Name | Type | Default | Description |
|---|---|---|---|
| label | string | 'Signature' | Accessible name for the pad. A `<canvas>` has no intrinsic name and no text inside it, so without this a screen reader announces nothing at all where the signature belongs. |
| hint | string | '' | Helper text under the pad. |
| size | SignaturePadSize | 'md' | Height preset for the drawing area. |
| penColor | string | '#111827' | Ink colour. Any CSS colour. |
| penWidth | number | 2 | Stroke width in CSS pixels, before pressure. |
| pressureSensitive | boolean | false | Vary the stroke with pointer pressure where the device reports it. Off by default: a mouse reports a constant 0.5, so on the commonest input it changes nothing but the arithmetic. |
| showActions | boolean | true | Show the Undo / Clear toolbar. |
| showBaseline | boolean | true | Show the baseline the signature sits on. |
| disabled | boolean | false | Nothing can be drawn, and the actions are hidden. |
| customClass | string | '' | Extra classes on the root element. |
| allowTyped | boolean | true | Offer a typed name beside the pad. |
| typedNameLabel | string | 'Or type your full name' | Label for the typed-name field. |
| typedName | string (two-way) | '' | The typed name. Two-way bindable. ⚠️ THE ATTESTATION WORDING IS THE HOST'S. "By typing my name I agree to…" is a legal sentence about a specific agreement, and a component that invents one is writing contracts it has not read. Put it in `hint`. |
| consentText | string | '' | The exact wording the signer is agreeing to. Rendered above the pad, and hashed into the attestation verbatim. |
| signerMetadata | Readonly<Record<string, string>> | null | null | What the host knows about the signer — account id, session, IP, authentication method. Opaque to the component, hashed into the attestation. |
Outputs
| Event | Payload type | Description |
|---|---|---|
| signatureChange | SignatureValue | Fires when a stroke ends, on undo, and on clear. |
| emptyChange | boolean | Fires the first time the pad goes from empty to signed, and back. |
| typedNameChange | string | Emits the new `typedName` whenever it changes — the output half of `[(typedName)]`. |
Customization & theming
- **Not a `ControlValueAccessor`, and it cannot be one.** A CVA moves ONE value in and out of a form control; `SignatureValue` carries the stroke geometry, the typed name, and — once `attest()` has run — the consent wording, its digest, the timestamp and the signer metadata. Collapsed into a `formControlName` the host gets back a picture, which is the failure the attestation work exists to fix: E-SIGN, UETA and eIDAS care about intent, attribution and association with the record signed.
- The contract instead: read `(signatureChange)` for a live draft, and call **`await signedValue()`** — not `value()` — for anything stored or sent. `attest()` returns `null` when the field is unsigned or no `consentText` was given, and a null is not an error to swallow: it means this signature is not a record. Validation is the host's; there is no `required` a form group can see, so use `(emptyChange)` or `isEmpty()`. `setValue()` restores a draft and keeps any attestation already on it — `value()` and `signedValue()` return the record as stored until a stroke, undo, clear or typed-name edit changes it — because re-attesting on load would date the record to the moment it was opened.
- Size the drawing area with `size` (sm / md / lg); ink with `penColor` and `penWidth`; `pressureSensitive` varies the stroke where the device reports pressure (a mouse always reports 0.5, so it is off by default).
- `showActions` toggles the Undo / Clear toolbar, `showBaseline` the signing rule, `hint` the helper text under the pad.
- Public methods: `undo()`, `clear()`, `value()`, `setValue()`, `toDataUrl()`, `toSvgString()`, `attest()`, `signedValue()`.
Best practices
- Do: Gate your submit on `emptyChange` or `isEmpty()`, never on "the canvas exists".
- Do: Store the value (`strokes` + the box) alongside any image — it is the only form that rescales, and the only one you can re-render.
- Do: Prefer `toSvgString()` for anything that will be printed; a PNG of a signature is resolution-bound the moment it is captured.
- Don't: Don't treat a returned `null` from an export as a failure — it means the pad is empty, which is information you want.
- Don't: Don't treat a drawn signature as identity or consent. It is a picture: it does not establish who drew it, that they meant to agree to anything, or what they were agreeing to. E-SIGN, UETA and eIDAS care about intent, attribution and association with the record signed — a stroke list addresses none of the three, and the affordance strongly implies otherwise.
- Do: Set `consentText` and call `attest()` at submit time. That is what turns the drawing into a record: the exact wording the signer was shown, an ISO-8601 timestamp, your `signerMetadata`, and a SHA-256 digest over all of it together with the strokes and the typed name.
- Don't: Don't treat the hash as tamper-evidence. It is computed in the signer's own browser by the party being attested, so anyone who can change the strokes can change the digest. What it does is bind the consent wording to the artefact — a hash over the image alone proves nothing about what was agreed to — and make accidental drift detectable. Counter-sign or timestamp it on the server, from a clock and a key the signer does not control.
- Do: Recompute with `svVerifySignature()` when you read a record back. It reports `unknown-format` separately from `mismatch`, because an old record read by a new build is a deployment mistake and not an accusation against a signer.
- Tip: The typed name (`allowTyped`, on by default) is both the keyboard-accessible route and usually the more legally meaningful artefact — a typed name attached to an authenticated session says more about intent and attribution than a squiggle from an unattended device.
Accessibility
- The canvas carries `role="img"` and is named by `label` — a `<canvas>` has no intrinsic name and no text inside it, so without that a screen reader announces nothing where the signature belongs.
- Undo and Clear are real buttons, keyboard reachable, and disabled rather than hidden while there is nothing to act on — a toolbar that appears mid-signature moves the pad under the user's hand.
- `touch-action: none` on the canvas stops the browser claiming the gesture for scrolling, which on a phone otherwise turns a signature into a dot and a page scroll.
- The baseline and the prompt are DOM siblings rather than painted onto the bitmap, so neither is exported with the signature.
Feedback on Signature Pad
Spotted a gap in this page, or something that didn't work as described? Comments are GitHub Discussions in the public styloviz/community repo, so posting needs a GitHub account.