Integrate Legalesign Signer into Your Website
The Signer component requires activation. Contact support to enable it for your team.
Current version: v1.1.7 — changelog
The Legalesign Signer is a platform-agnostic web component that provides a complete and seamless document signing experience from within your own app. It works with vanilla HTML, React, Vue, Angular, or any web framework.
Embed this component wherever your users need to sign documents — inside a CRM, client portal, or any internal application that renders HTML.
Installation
Load directly from the CDN (no install required):
<script type="module" src="https://cdn.legalesign.io/signer/latest/ls-signer.esm.js"></script>
React installation
Install via a package manager:
npm install legalesign-signer
# or
pnpm add legalesign-signer
Basic Integration
HTML & JavaScript
<ls-signer
recipient-id="abc123"
private-key="key"
session-id="session"
></ls-signer>
<script>
const signer = document.querySelector('ls-signer');
signer.addEventListener('signingSuccess', (e) => console.log('Signed:', e.detail.eventType, e.detail.documentId));
signer.addEventListener('signingFail', (e) => console.error('Failed:', e.detail.failureReason, e.detail.error));
signer.addEventListener('fieldChange', (e) => console.log('Field event:', e.detail.eventType));
</script>
React
import { useEffect, useState } from 'react';
import { LsSigner } from 'legalesign-signer/react';
import 'legalesign-signer/react.css';
function SigningPage() {
const [signerData, setSignerData] = useState(null);
useEffect(() => {
fetch('/your-backend/to-get-token')
.then(res => res.json())
.then(setSignerData);
}, []);
if (!signerData) return <p>Loading...</p>;
return (
<LsSigner
recipientId={signerData.recipientId}
privateKey={signerData.token}
sessionId={signerData.sessionId}
onSuccess={({ eventType, documentId }) => console.log(eventType, documentId)}
onFail={({ eventType, failureReason, error }) => console.error(eventType, failureReason, error)}
onChange={({ eventType, uuid, saved, field, document }) => console.log('Event:', eventType)}
/>
);
}
React requires react and react-dom (v18 or v19) as peer dependencies.
Vue
<template>
<ls-signer
ref="signer"
:recipient-id="recipientId"
:private-key="privateKey"
:session-id="sessionId"
/>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
const signer = ref(null);
function handleSuccess(e) {
console.log('Signed:', e.detail);
}
function handleFail(e) {
console.error('Failed:', e.detail);
}
onMounted(() => {
signer.value.addEventListener('signingSuccess', handleSuccess);
signer.value.addEventListener('signingFail', handleFail);
});
onBeforeUnmount(() => {
signer.value?.removeEventListener('signingSuccess', handleSuccess);
signer.value?.removeEventListener('signingFail', handleFail);
});
</script>
If using Vue with Vite, add isCustomElement: (tag) => tag.startsWith('ls-') to your Vue plugin config to suppress unknown element warnings.
Vue 3 lowercases event names on custom elements, so @signingSuccess won't work. Use addEventListener on the element ref as shown above.
Wrap <ls-signer> in <ClientOnly> to prevent SSR errors. Add the custom element config in nuxt.config.ts:
export default defineNuxtConfig({
vue: {
compilerOptions: {
isCustomElement: (tag) => tag.startsWith('ls-'),
},
},
});
Authentication
The signer component requires a short-lived token from your backend. There are two options:
- GraphQL
generateComponentToken— Call withcomponent: LS_SIGNERand either arecipientIdorsessionIdin the signer scope (provide one, not both). - REST API — Call
GET /signer/{signerId}/component-token/with your API key.
Both return the token and sessionId needed by the component. See Widget Authorization for the full server-side token flow.
Option 1: GraphQL (Node.js)
Using recipientId (most common — use when you know the recipient but don't yet have a session):
const response = await fetch('https://graphql.uk.legalesign.com/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.LEGALESIGN_API_KEY}`,
},
body: JSON.stringify({
query: `mutation MintSignerToken($input: GenerateComponentTokenInput!) {
generateComponentToken(input: $input) {
token
tokenType
sessionId
expiresIn
expiresAt
}
}`,
variables: {
input: {
component: 'LS_SIGNER',
signer: { recipientId: '<recipient-id>' }
}
}
})
});
const { data } = await response.json();
const { token, sessionId } = data.generateComponentToken;
Alternatively, if you already have a sessionId from a previous call:
variables: {
input: {
component: 'LS_SIGNER',
signer: { sessionId: '<session-id>' }
}
}
Option 2: REST API
const response = await fetch(
`https://eu-api.legalesign.com/api/v1/signer/${signerId}/component-token/`,
{
headers: {
Authorization: `Bearer ${process.env.LEGALESIGN_API_KEY}`,
},
}
);
const { token, sessionId } = await response.json();
Pass token as private-key and sessionId as session-id to the component.
Required Attributes
| Attribute | Type | Description |
|---|---|---|
recipient-id | string | Recipient identifier (base64, rec prefix, or UUID) |
private-key | string | Token returned by generateComponentToken |
session-id | string | Session ID returned by generateComponentToken |
Optional Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
colour | string | blue | Theme colour name (see Theming) |
style | string | `` | Style alterations (see Customer CSS) |
branding | boolean | true | Show Legalesign branding. Set to false to remove it. |
language | string | — | Force a specific language and hide the language switcher (see Internationalisation) |
Events
| React prop | Web component event | eventType | When |
|---|---|---|---|
onSuccess | signingSuccess | "success" | Document signed successfully |
onFail | signingFail | "failure" | Document signing failed, rejected, expired, cancelled, deleted, removed, or already completed elsewhere |
onChange | fieldChange | "ready" | Document and images fully loaded |
onChange | fieldChange | "save" | A field value was saved |
onChange | fieldChange | "select" | A field was selected or focused |
onSuccess / signingSuccess
Fired when the recipient successfully signs the document.
{
eventType: 'success';
documentId: string; // UUID of the signed document
recipientId: string; // UUID of the recipient
}
React:
<LsSigner
onSuccess={({ eventType, documentId, recipientId }) => {
console.log(eventType); // "success"
console.log(documentId);
console.log(recipientId);
}}
/>
Web component:
signer.addEventListener('signingSuccess', (e) => {
console.log(e.detail.eventType); // "success"
console.log(e.detail.documentId);
console.log(e.detail.recipientId);
});
onFail / signingFail
Fired when signing fails — either due to a document rejection or an error during the signing process (e.g. network error, expired session, document state conflict).
{
eventType: 'failure';
failureReason: FailureReason;
error: string; // Human-readable error message
documentId?: string; // UUID of the document (if available)
recipientId: string; // UUID of the recipient
}
failureReason | Cause |
|---|---|
documentExpired | Document state is "expired" or expires date is in the past |
sessionExpired | API returned 401 or 403 when loading the document |
cancelled | Document state is "cancelled" |
deleted | Document is_deleted flag is true |
rejected | Recipient rejected the document (is_rejected is true) |
removed | API returned 404 — document no longer exists |
documentCompleted | The sign API returned completed — the document was already signed, likely in another browser or device. The component shows an informational message to the recipient rather than an error. |
signingError | Any other error during load or signing |
React:
<LsSigner
onFail={({ eventType, failureReason, error, documentId, recipientId }) => {
console.log(eventType); // "failure"
console.log(failureReason); // e.g. "documentExpired"
console.log(error);
}}
/>
Web component:
signer.addEventListener('signingFail', (e) => {
console.log(e.detail.eventType); // "failure"
console.log(e.detail.failureReason); // e.g. "documentExpired"
console.log(e.detail.error);
});
onChange / fieldChange
All three eventType values share the same event. Use eventType to distinguish.
eventType: "ready"
Fired once when the document and all page images have fully loaded and the signing interface is ready for interaction.
{
eventType: 'ready';
document: {
documentId: string; // UUID of the document
name: string; // Document name
state: string; // Document state (e.g. "signing")
recipientId: string; // UUID of the recipient
recipientRole?: string; // Role of the recipient (e.g. "witness")
pageCount: number; // Number of pages
expires?: string; // Expiry date (ISO string, if set)
isApprover: boolean; // Whether the recipient is an approver
isWitness: boolean; // Whether the recipient is a witness
canReassign: boolean; // Whether the recipient can reassign
doOfferReject: boolean; // Whether the reject option is shown
};
}
React:
<LsSigner
onChange={({ eventType, document }) => {
if (eventType === 'ready') {
console.log('Ready to sign:', document.documentId, document.name);
console.log('Pages:', document.pageCount);
console.log('Expires:', document.expires);
}
}}
/>
Web component:
signer.addEventListener('fieldChange', (e) => {
if (e.detail.eventType === 'ready') {
console.log('Ready to sign:', e.detail.document.documentId, e.detail.document.name);
}
});
eventType: "save"
Fired after a field value has been saved to the server.
{
eventType: 'save';
uuid: string; // UUID of the field
saved: boolean; // Whether the save was successful
field: Record<string, any>; // Full field object (see Field Properties below)
}
React:
<LsSigner
onChange={({ eventType, uuid, saved, field }) => {
if (eventType === 'save') {
console.log(uuid, saved, field.value);
}
}}
/>
Web component:
signer.addEventListener('fieldChange', (e) => {
if (e.detail.eventType === 'save') {
console.log(e.detail.uuid, e.detail.saved, e.detail.field.value);
}
});
eventType: "select"
Fired when a field is selected or focused — triggered by user click, keyboard navigation, auto-forward, or sidebar interaction.
{
eventType: 'select';
uuid: string; // UUID of the selected field
field: Record<string, any>; // Full field object (see Field Properties below)
}
React:
<LsSigner
onChange={({ eventType, uuid, field }) => {
if (eventType === 'select') {
console.log('selected', uuid, field.fieldtype);
}
}}
/>
Web component:
signer.addEventListener('fieldChange', (e) => {
if (e.detail.eventType === 'select') {
console.log('selected', e.detail.uuid, e.detail.field.fieldtype);
}
});
Field Properties
The field object included in save and select events contains the following properties:
| Property | Type | Description |
|---|---|---|
uuid | string | Unique field identifier |
fieldtype | string | Type of field (e.g. "text", "signature", "date", "checkbox") |
value | string | Current field value |
state | boolean | Whether the field has been saved successfully |
optional | boolean | Whether the field is optional |
page | number | Page number the field appears on |
label | string | Field label (if set) |
validation | number | Validation type code |
logicaction | string | Logic action type ("1" = radio, "2" = sum, "3" = conditional) |
logicgroup | string | Logic group identifier |
mapto | string | Target group for logic (controller fields) |
auto | boolean | Whether the field is auto-populated |
dirty | boolean | Whether the field has unsaved local changes |
React Props
The React component uses camelCase props and callback props instead of DOM events:
| Prop | Type | Required | Description |
|---|---|---|---|
recipientId | string | ✅ | Recipient identifier |
privateKey | string | ✅ | Private key from the generateComponentToken mutation |
sessionId | string | ✅ | Session identifier |
colour | LsSignerColour | ❌ | Theme colour |
branding | boolean | ❌ | Show Legalesign branding. Defaults to true. |
language | string | ❌ | Force a specific language (see Internationalisation) |
onSuccess | (data: { eventType: 'success'; documentId: string; recipientId: string }) => void | ❌ | Called on successful signing |
onFail | (data: { eventType: 'failure'; failureReason: FailureReason; error: string; documentId?: string; recipientId: string }) => void | ❌ | Called on signing failure |
onChange | (data: { eventType: 'ready' | 'save' | 'select'; document?: DocumentSummary; uuid?: string; saved?: boolean; field?: Record<string, any> }) => void | ❌ | Called on field events and document ready |
Catch-all handler (debugging & prototyping)
Since every event includes eventType, you can wire a single function to all events:
React:
const handleSignerEvent = (e) => {
switch (e.eventType) {
case 'success': console.log('Signed:', e.documentId); break;
case 'failure': console.log('Failed:', e.failureReason, e.error); break;
case 'ready': console.log('Ready:', e.document?.name); break;
case 'save': console.log('Saved field:', e.uuid, e.saved); break;
case 'select': console.log('Selected field:', e.uuid); break;
}
};
<LsSigner
onSuccess={handleSignerEvent}
onFail={handleSignerEvent}
onChange={handleSignerEvent}
/>
Web component:
const handleSignerEvent = (e) => {
switch (e.detail.eventType) {
case 'success': console.log('Signed:', e.detail.documentId); break;
case 'failure': console.log('Failed:', e.detail.failureReason, e.detail.error); break;
case 'ready': console.log('Ready:', e.detail.document?.name); break;
case 'save': console.log('Saved field:', e.detail.uuid, e.detail.saved); break;
case 'select': console.log('Selected field:', e.detail.uuid); break;
}
};
signer.addEventListener('signingSuccess', handleSignerEvent);
signer.addEventListener('signingFail', handleSignerEvent);
signer.addEventListener('fieldChange', handleSignerEvent);
Theming
Set a colour theme with the colour prop. All shades are derived automatically.
Available Colours
pink · blue · purple · indigo · teal · green · lightblue · burnt · aubergine · red · yellow · cyan · lime · trueGreen
Omit the prop for the default blue.
<ls-signer colour="pink" ...></ls-signer>
<LsSigner colour="pink" ... />
The colour prop sets a data-ls-theme attribute on the component root. CSS custom properties define a 10–100 shade scale for each colour:
| Shade | Usage |
|---|---|
| 10 | Light backgrounds, subtle fills |
| 20 | Subtle borders |
| 30 | Focus rings |
| 60 | Primary colour (buttons, links, active states) |
| 70 | Hover state |
| 80 | Dark/strong accents |
Custom CSS
The component exposes CSS custom properties that can be overridden to customise appearance beyond the colour theme. This set of properties is limited at present, contact us for more.
<ls-signer
style="--ls-font-family: 'Inter', sans-serif; --ls-color-primary-60: #e91e63;"
recipient-id="abc123"
private-key="key"
session-id="session"
></ls-signer>
Available Properties
| Property | Default | Description |
|---|---|---|
--ls-font-family | 'IBM Plex Sans', sans-serif | Primary font family |
--ls-color-primary-10 to --ls-color-primary-100 | — | Full primary colour scale (overrides theme) |
--ls-color-error | #f64a44 | Error state colour |
--ls-color-error-light | #fff0f0 | Error background |
--ls-color-success | #46dbaa | Success state colour |
--ls-color-success-light | #effff9 | Success background |
--ls-color-warning | #fad232 | Warning state colour |
--ls-color-warning-light | #fffcef | Warning background |
--ls-color-border | #d8d9dc | Default border colour |
--ls-color-border-subtle | #e0e2e5 | Subtle border colour |
--ls-color-bg-subtle | #f7f8fa | Subtle background colour |
Note: For full custom CSS injection (targeting internal elements directly, overriding font sizes, border radius, spacing, etc.), this is a planned future feature. Please get in touch if you're interested.
Internationalisation
The component supports 17 languages with translations bundled into the build. Language is detected automatically from the browser. A built-in language switcher in the signing UI allows the recipient to change language at any time.
Set the language attribute to force a specific language. When provided, the built-in language switcher is hidden and browser detection is bypassed.
<ls-signer language="fr" recipient-id="abc123" private-key="key" session-id="session"></ls-signer>
<LsSigner language="fr" recipientId="abc123" privateKey="key" sessionId="session" />
Priority order: language prop → browser language → English fallback.
| Code | Language | Code | Language |
|---|---|---|---|
en | English | nl | Dutch |
fr | French | fi | Finnish |
bg | Bulgarian | it | Italian |
es | Spanish | he | Hebrew |
de | German | sv | Swedish |
gs | Scottish Gaelic | cy | Welsh |
ar | Arabic | is | Icelandic |
el | Greek | iw | Hebrew (legacy) |
pt | Portuguese | ||
ro | Romanian |
CDN Versioning
The CDN URL supports both latest and pinned version numbers:
<!-- Always get the most recent version (recommended) -->
<script type="module" src="https://cdn.legalesign.io/signer/latest/ls-signer.esm.js"></script>
<!-- Pin to a specific version -->
<script type="module" src="https://cdn.legalesign.io/signer/v1.1.7/ls-signer.esm.js"></script>
We recommend using latest — this ensures your integration automatically receives bug fixes, security patches, and new features. Use a pinned version if you need to lock to a known working release during a QA freeze or controlled rollout. See the full version history on npm for available versions.
Complete Example
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document Signing</title>
<script type="module" src="https://cdn.legalesign.io/signer/latest/ls-signer.esm.js"></script>
</head>
<body>
<ls-signer
recipient-id="abc123"
private-key="key"
session-id="session"
colour="teal"
></ls-signer>
<script>
const signer = document.querySelector('ls-signer');
signer.addEventListener('signingSuccess', (event) => {
console.log('Document signed:', event.detail.documentId);
window.location.href = '/thank-you';
});
signer.addEventListener('signingFail', (event) => {
console.error('Signing failed:', event.detail.failureReason, event.detail.error);
});
signer.addEventListener('fieldChange', (event) => {
if (event.detail.eventType === 'ready') {
console.log('Ready to sign:', event.detail.document.name);
}
});
</script>
</body>
</html>
Browser Support
- Chrome/Edge (latest)
- Firefox (latest)
- Safari (latest)
- Mobile browsers (iOS Safari, Chrome Mobile)
Changelog
| Version | Date | Summary |
|---|---|---|
| v1.1.7 | 20 Oct 2026 | documentCompleted failure reason; field save improvements; positioning fixes; improved failure event capture |
| v1.1.0 | 22 Jul 2026 | onChange / fieldChange event; language prop; conditional fields; auto-jump; QES |
| v1.0.12 | 20 Jul 2026 | onChange / fieldChange event with ready, save, select; failureReason values; regex validation |
| v1.0.11 | 15 Jul 2026 | language prop; conditional, dropdown, checkbox fields; phone input; field ordering |
| v1.0.6 | 9 Jun 2026 | branding prop added |
| v1.0.3 | 15 May 2026 | colour prop; CSS custom properties; bug fixes |
| v1.0.0 | 11 May 2026 | Initial stable release |