Working with Signatures
Learn how to implement digital signatures using the SignaturePad component and signature fields in the PDFViewer.
Signature Modes
The MIMS SDK supports three signature input modes:
- Draw - Users draw their signature with mouse or touch
- Type - Users type their name and select a signature font
- Upload - Users upload an image of their signature
Using SignaturePad Component
The SignaturePad component provides a standalone signature capture interface:
Standalone SignaturePadtsx
import { SignaturePad } from '@mims/sdk-react';
import { useState } from 'react';
function SignatureCapture() {
const [signature, setSignature] = useState<string | null>(null);
const handleSave = (imageData: string) => {
setSignature(imageData);
console.log('Signature captured:', imageData.substring(0, 50) + '...');
};
const handleClear = () => {
setSignature(null);
};
return (
<div className="signature-capture">
<SignaturePad
width={400}
height={200}
penColor="#000000"
backgroundColor="#ffffff"
onSave={handleSave}
onClear={handleClear}
/>
{signature && (
<div className="preview mt-4">
<p>Preview:</p>
<img src={signature} alt="Signature preview" className="border" />
</div>
)}
</div>
);
}Signature Fields in PDF
Add signature fields to documents for users to sign specific areas:
Adding Signature Fieldstsx
import { useDocumentStore } from '@mims/sdk-react';
import { nanoid } from 'nanoid';
function AddSignatureField() {
const currentPage = useDocumentStore((s) => s.currentPage);
const addField = useDocumentStore((s) => s.addField);
const handleAddSignature = () => {
addField({
id: nanoid(),
type: 'signature',
page: currentPage,
// Position at bottom of page (coordinates are 0-1 relative)
x: 0.1,
y: 0.75,
width: 0.35,
height: 0.12,
required: true,
editable: true,
label: 'Your Signature',
signatureMode: 'draw',
});
};
return (
<button onClick={handleAddSignature}>
Add Signature Field
</button>
);
}Signature Field Definition
| Prop | Type | Default | Description |
|---|---|---|---|
typerequired | "signature" | — | Field type identifier. |
signatureMode | "draw" | "type" | "upload" | "draw" | How the user can input their signature. |
required | boolean | false | Whether the signature is required for submission. |
Signature Field Value
| Prop | Type | Default | Description |
|---|---|---|---|
typerequired | "signature" | — | Value type identifier. |
imageData | string | — | Base64 encoded PNG image of the signature. |
pathData | string | — | SVG path data for vector representation. |
typedText | string | — | Typed signature text (when using type mode). |
Custom Signature Modal
Create a custom modal for signature capture with multiple input options:
SignatureModal.tsxtsx
'use client';
import { useState } from 'react';
import { SignaturePad, useDocumentStore } from '@mims/sdk-react';
interface SignatureModalProps {
fieldId: string;
isOpen: boolean;
onClose: () => void;
}
type SignatureTab = 'draw' | 'type' | 'upload';
export function SignatureModal({ fieldId, isOpen, onClose }: SignatureModalProps) {
const [activeTab, setActiveTab] = useState<SignatureTab>('draw');
const [typedName, setTypedName] = useState('');
const [selectedFont, setSelectedFont] = useState('Dancing Script');
const setFieldValue = useDocumentStore((s) => s.setFieldValue);
if (!isOpen) return null;
const handleDrawSave = (imageData: string) => {
setFieldValue(fieldId, {
type: 'signature',
imageData,
});
onClose();
};
const handleTypeSave = () => {
if (!typedName.trim()) return;
// Generate signature image from typed text
const canvas = document.createElement('canvas');
canvas.width = 400;
canvas.height = 150;
const ctx = canvas.getContext('2d')!;
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.font = `48px "${selectedFont}"`;
ctx.fillStyle = '#000000';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(typedName, canvas.width / 2, canvas.height / 2);
const imageData = canvas.toDataURL('image/png');
setFieldValue(fieldId, {
type: 'signature',
imageData,
typedText: typedName,
});
onClose();
};
const handleUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
const imageData = event.target?.result as string;
setFieldValue(fieldId, {
type: 'signature',
imageData,
});
onClose();
};
reader.readAsDataURL(file);
};
const fonts = [
'Dancing Script',
'Great Vibes',
'Pacifico',
'Caveat',
];
return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg w-full max-w-lg p-6">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-semibold">Add Your Signature</h2>
<button onClick={onClose} className="text-neutral-500">×</button>
</div>
{/* Tabs */}
<div className="flex gap-2 mb-4">
{(['draw', 'type', 'upload'] as const).map((tab) => (
<button
key={tab}
onClick={() => setActiveTab(tab)}
className={`px-4 py-2 rounded ${
activeTab === tab
? 'bg-emerald-500 text-white'
: 'bg-neutral-100'
}`}
>
{tab.charAt(0).toUpperCase() + tab.slice(1)}
</button>
))}
</div>
{/* Draw Tab */}
{activeTab === 'draw' && (
<SignaturePad
width={400}
height={200}
onSave={handleDrawSave}
/>
)}
{/* Type Tab */}
{activeTab === 'type' && (
<div className="space-y-4">
<input
type="text"
value={typedName}
onChange={(e) => setTypedName(e.target.value)}
placeholder="Type your full name"
className="w-full p-3 border rounded"
/>
<div className="space-y-2">
<label className="text-sm text-neutral-600">Select font:</label>
<div className="grid grid-cols-2 gap-2">
{fonts.map((font) => (
<button
key={font}
onClick={() => setSelectedFont(font)}
className={`p-3 border rounded text-2xl ${
selectedFont === font ? 'border-emerald-500 bg-emerald-50' : ''
}`}
style={{ fontFamily: font }}
>
{typedName || 'Preview'}
</button>
))}
</div>
</div>
<button
onClick={handleTypeSave}
disabled={!typedName.trim()}
className="w-full py-2 bg-emerald-500 text-white rounded disabled:opacity-50"
>
Apply Signature
</button>
</div>
)}
{/* Upload Tab */}
{activeTab === 'upload' && (
<div className="border-2 border-dashed rounded-lg p-8 text-center">
<input
type="file"
accept="image/*"
onChange={handleUpload}
className="hidden"
id="signature-upload"
/>
<label htmlFor="signature-upload" className="cursor-pointer">
<p className="text-neutral-600 mb-2">
Click to upload or drag and drop
</p>
<p className="text-sm text-neutral-400">
PNG, JPG up to 2MB
</p>
</label>
</div>
)}
</div>
</div>
);
}Signature Validation
Implement validation to ensure signatures meet requirements:
Signature Validationtsx
function validateSignature(value: SignatureFieldValue | null): string[] {
const errors: string[] = [];
if (!value) {
errors.push('Signature is required');
return errors;
}
// Must have either image data or typed text
if (!value.imageData && !value.typedText) {
errors.push('Please draw, type, or upload a signature');
}
// Check minimum image size (avoid blank signatures)
if (value.imageData) {
const img = new Image();
img.src = value.imageData;
// Simple check: base64 string should be substantial
if (value.imageData.length < 1000) {
errors.push('Signature appears to be blank');
}
}
return errors;
}
// Use in submission
function validateBeforeSubmit() {
const fields = useDocumentStore.getState().fields;
fields.forEach((field, id) => {
if (field.definition.type === 'signature' && field.definition.required) {
const errors = validateSignature(field.value as SignatureFieldValue);
if (errors.length > 0) {
// Update field errors
useDocumentStore.getState().updateFieldDefinition(id, { errors });
}
}
});
}Legal Considerations
Digital signatures may have legal requirements depending on your jurisdiction. Consult with legal counsel about compliance with e-signature laws like ESIGN, UETA, or eIDAS.
Signature Storage Format
When submitted, signatures are included in the payload as base64 images:
Submission Payload (signature field)json
{
"id": "sig_abc123",
"type": "signature",
"page": 1,
"position": {
"x": 0.1,
"y": 0.75,
"width": 0.35,
"height": 0.12
},
"value": {
"type": "signature",
"imageData": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...",
"typedText": null
}
}Signature Appearance
Customize the appearance of signature fields:
Styled Signature Fieldtsx
function SignatureFieldOverlay({ field }: { field: FieldState }) {
const value = field.value as SignatureFieldValue | null;
const selectField = useDocumentStore((s) => s.selectField);
const isEmpty = !value?.imageData && !value?.typedText;
const isRequired = field.definition.required;
return (
<div
onClick={() => selectField(field.definition.id)}
className={`
absolute border-2 rounded cursor-pointer transition-colors
${isEmpty
? 'border-dashed border-neutral-300 bg-neutral-50/50'
: 'border-solid border-emerald-500 bg-emerald-50/30'
}
${isRequired && isEmpty ? 'border-red-300 bg-red-50/30' : ''}
`}
style={{
left: `${field.definition.x * 100}%`,
top: `${field.definition.y * 100}%`,
width: `${field.definition.width * 100}%`,
height: `${field.definition.height * 100}%`,
}}
>
{isEmpty ? (
<div className="flex items-center justify-center h-full text-neutral-400">
<span>Click to sign</span>
{isRequired && <span className="text-red-500 ml-1">*</span>}
</div>
) : (
<img
src={value!.imageData}
alt="Signature"
className="w-full h-full object-contain"
/>
)}
</div>
);
}