Signature Pad
Canvas signature capture that participates in forms — freehand strokes serialize to a PNG data-URL and submit as the field value.
<arc-signature-pad> Overview
>
SignaturePad is a bordered drawing surface for capturing a handwritten signature. While blank, a muted "Sign here" baseline invites the first stroke and disappears the moment ink goes down. The pen line scales with stroke velocity — slow, deliberate movement thickens it and fast flicks thin it — so a mouse or finger produces something that reads as handwriting rather than a polyline. The pen color follows `--text-primary` by default and accepts any CSS color, including a `var()` reference, through the `pen-color` attribute.
The component is a real form control. Each completed stroke serializes the whole canvas to a PNG data-URL and submits it under `name`; a blank pad submits nothing, and `required` on a blank pad reports `valueMissing` like any other input. The stroke is the edit unit: `arc-input` and `arc-change` fire together once per completed stroke, never per point, each carrying the data-URL in `event.detail.value`. Once signed, a small ghost clear button appears in the top corner; clearing empties the value, fires `arc-clear`, and brings the placeholder back.
An honest note on accessibility: signing by hand is inherently a pointer gesture, and the pad offers no keyboard path to produce a signature. The canvas exposes `role="img"` with an accessible name that announces its signed or empty state, it stays focusable, and the clear button is keyboard-reachable — but if your form must be completable without a pointer, you need to offer an equivalent alongside the pad, such as a type-to-sign text field or a file upload. The component does not simulate one.Guidelines
When to use
- Give the pad a `label` — it doubles as the accessible name of the canvas
- Set `required` when a signature is mandatory; the pad reports `valueMissing` while blank exactly like a native input
- Offer a keyboard-accessible alternative (type-to-sign, upload) next to the pad when the form must be completable without a pointer — the pad itself is pointer-only by nature
- Listen for `arc-change` to persist or preview the signature; each completed stroke delivers the full, current image
- Use `readonly` to display a captured signature that still submits but can no longer be altered
When not to use
- Do not reflect or store the value as an attribute — a data-URL is far too large; read the `value` property or the event detail instead
- Do not treat a signature image as proof of identity on its own — pair it with real authentication when it matters legally
- Do not listen per-point for drawing progress; the component deliberately stays silent until a stroke completes
- Do not hide the clear button behind your own chrome — a signer who slips needs an obvious way to start over
- Avoid very small pads; under about 240px of width there is not enough room for a natural signing motion
Features
- Freehand drawing with pointer capture — mouse, touch, and stylus all work, and touch never scrolls the page mid-stroke
- Velocity-scaled pen width (up to 40% thicker or thinner around the `pen-width` base) with midpoint-smoothed curves for a natural line
- Serializes to a PNG data-URL after every completed stroke; `toDataURL(type)` exports other formats on demand
- Full form participation: submits under `name`, `required` + blank reports `valueMissing`, `form.reset()` restores the initial state
- The stroke is the edit unit — `arc-input` and `arc-change` fire once per stroke, never per point
- Ghost clear button appears once signed; clearing fires `arc-clear` and restores the "Sign here" placeholder
- Pen color resolves CSS custom properties at stroke time, so it follows theme changes without configuration
- Crisp on high-DPI screens — the backing store tracks `devicePixelRatio`, and completed strokes survive a resize
- Setting `value` from script draws the image back onto the canvas, so a saved signature can be restored for review
Preview
Signature
Usage
This component requires JavaScript. No pure HTML/CSS version is available — use the Web Component directly or a framework wrapper.
<form>
<arc-signature-pad
label="Signature"
name="signature"
required
></arc-signature-pad>
</form>
<script>
const pad = document.querySelector('arc-signature-pad');
// Fired once per completed stroke with the PNG data-URL
pad.addEventListener('arc-change', (e) => {
console.log('Signature updated:', e.detail.value.slice(0, 40) + '…');
});
pad.addEventListener('arc-clear', () => {
console.log('Signature cleared');
});
</script> import { SignaturePad } from '@arclux/arc-ui-react';
export default function Example() {
return (
<SignaturePad
label="Signature"
name="signature"
required
onArcChange={(e) => console.log('Signature:', e.detail.value)}
onArcClear={() => console.log('Cleared')}
/>
);
} <script setup>
import { SignaturePad } from '@arclux/arc-ui-vue';
</script>
<template>
<SignaturePad
label="Signature"
name="signature"
required
@arc-change="(e) => console.log('Signature:', e.detail.value)"
@arc-clear="() => console.log('Cleared')"
/>
</template> <script>
import { SignaturePad } from '@arclux/arc-ui-svelte';
</script>
<SignaturePad
label="Signature"
name="signature"
required
on:arc-change={(e) => console.log('Signature:', e.detail.value)}
on:arc-clear={() => console.log('Cleared')}
/> import { Component } from '@angular/core';
import { SignaturePad } from '@arclux/arc-ui-angular';
@Component({
imports: [SignaturePad],
template: `
<arc-signature-pad
label="Signature"
name="signature"
required
(arc-change)="onSign($event)"
(arc-clear)="onClear()"
></arc-signature-pad>
`,
})
export class MyComponent {
onSign(e: CustomEvent) {
console.log('Signature:', e.detail.value);
}
onClear() {
console.log('Cleared');
}
} import { SignaturePad } from '@arclux/arc-ui-solid';
export default function Example() {
return (
<SignaturePad
label="Signature"
name="signature"
required
onArcChange={(e) => console.log('Signature:', e.detail.value)}
onArcClear={() => console.log('Cleared')}
/>
);
} import { SignaturePad } from '@arclux/arc-ui-preact';
export default function Example() {
return (
<SignaturePad
label="Signature"
name="signature"
required
onArcChange={(e) => console.log('Signature:', e.detail.value)}
onArcClear={() => console.log('Cleared')}
/>
);
} API
-
SPEED_FULLnumber1.5 - Stroke speed (CSS px per ms) at which the pen reaches its thinnest.
-
valuestring'' - The signature as a PNG data-URL, empty string while the pad is blank. Updated after every completed stroke. Setting it from script draws the image onto the canvas (client-side only). Not reflected — a data-URL is far too large to live in an attribute.
-
namestring'' - Form field name the data-URL submits under.
-
labelstring'' - Label text displayed above the pad in the label typography role. Also feeds the canvas's accessible name.
-
disabledbooleanfalse - Disables interaction, reducing opacity and blocking pointer events. The pad leaves the tab order.
-
pen-colorstring'' - Pen color as any CSS color, including a `var()` expression, resolved against the canvas at stroke time. Attribute: `pen-color`. Defaults to the resolved value of `--text-primary`.
-
pen-widthnumber2 - Base pen width in CSS pixels. The drawn line scales with stroke velocity — up to 40% thicker on slow, deliberate movement and 40% thinner on fast flicks. Attribute: `pen-width`. Default 2.
-
readonlybooleanfalse - Prevents drawing and hides the clear button while the pad stays focusable and the value still submits.
-
requiredbooleanfalse - When true and the pad is blank, the control is invalid with `valueMissing`.
-
formAssociatedbooleantrue -
propertiesobject{ required: { type: Boolean, reflect: true }, readonly: { type: Boolean, reflect: true }, } - Lit merges static properties up the prototype chain, so every consumer gets these without declaring them. `required` participates in constraint validation below; `readonly` reflects for styling and is enforced by each component's interaction handlers (the mixin can't know which gestures mutate state).
-
autoValidatesbooleantrue - Components that run their own constraint-validation logic (pattern checks, range checks) opt out of the automatic required sync by overriding this to false, and own the whole validity flag set instead.
-
form -
validity -
validationMessage
Events
-
arc-clear - Fired when the pad is cleared, via the clear button or the clear() method.
-
arc-inputdetail: { value: string } - Fired once per completed stroke with the serialized data-URL. A stroke is the edit unit — nothing fires per point while the pen is down.
-
arc-changedetail: { value: string } - Fired when the pointer session ends and the value serializes. A stroke is a discrete gesture, so each stroke end fires arc-input then arc-change together.
See Also
- File Upload Drag-and-drop file upload zone with preview.
- Image Cropper Crop-before-upload control with a draggable, resizable crop rectangle, aspect-ratio locking, zoom, and canvas export at natural image resolution.
- Color Picker Full-featured color picker with a saturation/lightness area, hue slider, hex input, and optional preset swatches.