Electronic Signature Implementation (Drawing/Input) on Website
Electronic signature in the "draw or type" format is the simplest way to obtain user consent on a document. Don't confuse with qualified electronic signature (QES): here we're talking about visual signature without cryptography, used in contracts, agreements, and work completion certificates.
Three Input Methods
Draw — user draws signature with mouse or stylus on canvas. Most familiar format for majority of users.
Type — enter name, displayed in selected "handwritten" font (Caveat, Dancing Script, Pacifico).
Upload Image — upload photo/scan of own signature.
Implementation via signature_pad
Library signature_pad is the standard for canvas-signature:
import SignaturePad from 'signature_pad';
import { useRef, useEffect, useState } from 'react';
function SignatureCanvas({ onSave, onClear }) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const padRef = useRef<SignaturePad | null>(null);
useEffect(() => {
const canvas = canvasRef.current!;
padRef.current = new SignaturePad(canvas, {
backgroundColor: 'rgb(255, 255, 255)',
penColor: 'rgb(0, 0, 0)',
minWidth: 0.5,
maxWidth: 2.5,
throttle: 16, // ~60fps
});
// Scaling for Retina displays
function resizeCanvas() {
const ratio = Math.max(window.devicePixelRatio || 1, 1);
canvas.width = canvas.offsetWidth * ratio;
canvas.height = canvas.offsetHeight * ratio;
const ctx = canvas.getContext('2d')!;
ctx.scale(ratio, ratio);
padRef.current!.clear();
}
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
return () => window.removeEventListener('resize', resizeCanvas);
}, []);
const handleSave = () => {
if (padRef.current!.isEmpty()) return;
const dataUrl = padRef.current!.toDataURL('image/png');
onSave(dataUrl);
};
return (
<div className="border-2 border-dashed border-gray-300 rounded-lg">
<canvas
ref={canvasRef}
className="w-full h-40 touch-none" // touch-none important for mobile
/>
<div className="flex gap-2 p-2">
<button onClick={() => { padRef.current!.clear(); onClear(); }}>Clear</button>
<button onClick={handleSave} className="btn-primary">Sign</button>
</div>
</div>
);
}
Text Signature Input
const SIGNATURE_FONTS = [
{ name: 'Caveat', label: 'Handwritten 1' },
{ name: 'Dancing Script', label: 'Handwritten 2' },
{ name: 'Pacifico', label: 'Calligraphy' },
];
function TextSignature({ name, onSave }) {
const [selectedFont, setSelectedFont] = useState(SIGNATURE_FONTS[0]);
const canvasRef = useRef<HTMLCanvasElement>(null);
const renderToCanvas = (font: string, text: string) => {
const canvas = canvasRef.current!;
const ctx = canvas.getContext('2d')!;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.font = `48px "${font}"`;
ctx.fillStyle = '#1a1a1a';
ctx.fillText(text, 20, 70);
return canvas.toDataURL('image/png');
};
return (
<div>
<div className="flex gap-2 mb-3">
{SIGNATURE_FONTS.map(font => (
<button
key={font.name}
style={{ fontFamily: font.name }}
onClick={() => setSelectedFont(font)}
className={selectedFont.name === font.name ? 'border-blue-500 border-2' : ''}
>
{name}
</button>
))}
</div>
<canvas ref={canvasRef} width={400} height={100} className="border rounded" />
<button onClick={() => onSave(renderToCanvas(selectedFont.name, name))}>
Apply signature
</button>
</div>
);
}
Embedding Signature in Document
After obtaining signature as base64 PNG, embed it in document. For PDF — via PDF-lib:
import { PDFDocument } from 'pdf-lib';
async function embedSignatureInPdf(pdfBytes, signatureDataUrl, position) {
const pdfDoc = await PDFDocument.load(pdfBytes);
const pages = pdfDoc.getPages();
const targetPage = pages[position.page];
const signatureBytes = Buffer.from(signatureDataUrl.split(',')[1], 'base64');
const signatureImage = await pdfDoc.embedPng(signatureBytes);
targetPage.drawImage(signatureImage, {
x: position.x,
y: position.y,
width: position.width,
height: position.height,
});
return await pdfDoc.save();
}
Storage and Audit
Signature as image is saved to S3. Database records: user_id, timestamp, IP address, user-agent, document_id, document hash. This provides basic audit trail.
Timeframe
Component with three signature methods (draw, text, upload) and PDF embedding — 2–3 days.







