JavaScript Signature Pad
@lemonadejs/signature · ✓ 23 contract checks · framework-agnostic · zero dependencies
<Signature /> — canvas signature pad, ported from the v5 plugin
Full behavioral parity with v5: pointer drawing (mouse + touch), the v5 value format (a flat list of [x, y] points with ‘1’ separators between strokes), line thickness, instructions text, disabled, and the full replay algorithm (commit): clear + redraw the whole value as one path — including the v5 quirk where a click stroke becomes a round dot.
v5 → v6 mapping: value (two-way) → bind; value (initial) stays value;
line/width/height/instructions/disabled unchanged; onchange/onload
unchanged (onchange now receives the value, not the instance);
getValue/setValue/getImage move to the api surface (props.ref), plus
clear() = setValue([]). New: color (v5 hardcoded #000), name (renders
a hidden input so the pad participates in forms — v5 only patched .val()
onto the canvas), and clearlabel (renders a real, keyboard-operable
clear <button> — WCAG 2.1.1: clearing must not require a pointer).
Accessibility: drawing is a path-of-movement input (WCAG 2.1.1 essential-exception territory) — the canvas is exposed as role=“img” (“Signature pad”) and the host application should offer keyboard users an alternative (e.g. a typed signature) alongside the pad.
jsdom has no canvas: a null 2d context downgrades the pad to a no-op.
Example
import { html } from 'lemonadejs';
import Signature from '@lemonadejs/signature';
const App = (props, { state }) => {
const strokes = state([]);
const image = state('');
let pad;
return html`<div>
<${Signature} bind="${strokes}" width="400" height="160" color="#1d4ed8"
instructions="Sign here to accept the delivery" ref="${(api) => (pad = api)}" />
<p>
<button onclick="${() => { pad.clear(); image.value = ''; }}">Clear</button>
<button onclick="${() => (image.value = pad.getImage())}">Export PNG</button>
${() => (strokes.value.length ? ' Signature captured (' + strokes.value.length + ' points)' : ' Waiting for a signature')}
</p>
${() => (image.value ? html`<img src="${image.value}" alt="Signature" style="border:1px solid #ddd" />` : '')}
</div>`;
};Installation
npm install @lemonadejs/signature
import Signature from '@lemonadejs/signature';
import '@lemonadejs/signature/style.css';
Three deployment forms, one component:
html`<${Signature} />` // by value (no registration)
setComponents({ Signature }); // then <Signature /> by name anywhere
createWebComponent(Signature); // <lm-signature> in plain HTML/any framework
Props
Every declared prop arrives as a live state — pass a value for a snapshot or a state for a two-way live wire. Attribute strings are coerced to the declared type.
| Prop | Type | Default | Description |
|---|---|---|---|
bind | array | — | Two-way bound value. .set() fires onchange; plain assignment is silent. two-way stroke data (v5: value) |
value | array | — | initial strokes when unbound |
width | number | 0 | canvas width (0: browser default) |
height | number | 0 | canvas height (0: browser default) |
line | number | 0 | stroke thickness, 3 when unset (v5) |
color | string | '' | stroke color, #000 when unset (v5 fixed) |
name | string | '' | form field name (hidden input, JSON value) |
instructions | string | '' | helper text under the canvas |
clearlabel | string | '' | when set, renders a keyboard-operable clear <button> |
disabled | boolean | false | blocks drawing |
Events
All event names are lowercase (the platform convention — LJS-305 warns otherwise).
onchange— fires on stroke end, setValue and clearonload— fires once the canvas is ready (v5)
API
import { ref } from 'lemonadejs';
const signature = ref();
html`<${Signature} ref="${signature}" />`;
// signature.current.getValue(...) · signature.current.setValue(...) · signature.current.getImage(...) · signature.current.clear(...)
getValue()setValue()getImage()clear()
Styling
All classes follow the lm-signature-* convention; visual variants are data-*
attributes on the root. Override freely — there is no styling engine to fight.
Contract
The machine-readable schema ships with the package:
import contract from '@lemonadejs/signature/contract.json';
verify.json carries the conformance proof produced by verify(Signature).
Looking for the v5 plugin? See the archived v5 documentation.