React SDK
The @korala/react package provides React components and hooks for embedding document signing directly in your application.
Installation
npm install @korala/react
# or
yarn add @korala/react
# or
pnpm add @korala/reactQuick Start
import { KoralaSigner } from '@korala/react';
function SigningPage({ token }: { token: string }) {
return (
<KoralaSigner
token={token}
signingUrl="https://app.korala.ai"
style={{ width: '100%', height: '800px' }}
onSigned={(data) => {
console.log('Document signed!', data.documentId);
}}
onError={(data) => {
console.error('Signing error:', data.message);
}}
/>
);
}Components
KoralaSigner
Renders an embedded signing iframe. The signer can view the document and complete all assigned fields without leaving your app.
<KoralaSigner
token={signingToken}
signingUrl="https://app.korala.ai"
className="signing-frame"
style={{ width: '100%', height: '100%' }}
allowedOrigins={['https://app.korala.ai']}
onReady={(data) => console.log('Ready:', data.version)}
onLoaded={(data) => console.log('Document loaded:', data.documentName)}
onViewed={(data) => console.log('Viewed at:', data.viewedAt)}
onFieldFilled={(data) => console.log(`${data.filledFields}/${data.totalRequiredFields} fields`)}
onSigned={(data) => console.log('Signed:', data.documentId)}
onDeclined={(data) => console.log('Declined:', data.reason)}
onError={(data) => console.error(data.message)}
/>Props
| Prop | Type | Required | Description |
|---|---|---|---|
token | string | Yes | Signer access token (from the API) |
signingUrl | string | No | Base URL of the signing app. Defaults to https://korala.ai |
className | string | No | CSS class for the iframe |
style | CSSProperties | No | Inline styles for the iframe |
allowedOrigins | string[] | No | Restrict which origins can send events. Accepts all if omitted |
theme | KoralaSignerTheme | No | Customize accent color, border radius, and font |
onReady | (data) => void | No | Iframe initialized |
onLoaded | (data) => void | No | Document loaded in viewer |
onViewed | (data) => void | No | Signer viewed the document |
onFieldFilled | (data) => void | No | A field was filled |
onSigned | (data) => void | No | Signer completed signing |
onDeclined | (data) => void | No | Signer declined to sign |
onError | (data) => void | No | An error occurred |
Theme Customization
Match the signing UI to your brand by passing a theme prop. Only accent-colored elements (buttons, field borders, focus rings) are replaced — semantic colors (green success, red error, amber waiting) remain unchanged.
import { KoralaSigner } from '@korala/react';
import type { KoralaSignerTheme } from '@korala/react';
const theme: KoralaSignerTheme = {
accentColor: '#2563EB', // hex color
borderRadius: 8, // 0-24 px
fontFamily: 'Inter, system-ui, sans-serif', // CSS font-family
};
<KoralaSigner
token={signingToken}
theme={theme}
style={{ width: '100%', height: '100%' }}
onSigned={(data) => console.log('Signed:', data.documentId)}
/>Theme Properties
| Property | Type | Default | Description |
|---|---|---|---|
accentColor | string | #D4503C | Hex color for buttons, borders, and focus states |
borderRadius | number | 0 | Border radius in pixels (clamped 0-24) for buttons, modals, and inputs |
fontFamily | string | inherit | CSS font-family applied to the signing UI |
All properties are optional. When no theme is provided, the signing UI uses the default Korala styling.
Default theme (coral accent, zero radius):

Custom blue theme (accentColor: '#2563EB', borderRadius: 8):

KoralaSignaturePad
A canvas-based signature drawing pad. Useful for capturing signatures before uploading them via the API.
import { KoralaSignaturePad } from '@korala/react';
function CaptureSignature() {
const [signature, setSignature] = useState<string | null>(null);
return (
<KoralaSignaturePad
width={600}
height={200}
penColor="#000000"
onSignatureCreated={({ imageDataUrl }) => {
setSignature(imageDataUrl);
}}
onClear={() => setSignature(null)}
/>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
width | number | 600 | Canvas width in pixels |
height | number | 200 | Canvas height in pixels |
penColor | string | #000000 | Stroke color |
className | string | — | CSS class for the container |
style | CSSProperties | — | Inline styles |
onSignatureCreated | (data) => void | — | Called with { imageDataUrl } (base64 PNG) when a stroke ends |
onClear | () => void | — | Called when the canvas is cleared |
Hooks
useKoralaEvents
Track the signing status and event history from any component. Useful when you need signing state outside the KoralaSigner component.
import { useKoralaEvents } from '@korala/react';
function SigningStatus() {
const { status, events, lastEvent } = useKoralaEvents({
allowedOrigins: ['https://app.korala.ai'],
});
return (
<div>
<p>Status: {status}</p>
<p>Events received: {events.length}</p>
{status === 'signed' && <p>Document signed successfully!</p>}
</div>
);
}Status Values
| Status | Description |
|---|---|
loading | Initial state, waiting for iframe |
ready | Iframe initialized |
loaded | Document loaded in viewer |
viewed | Signer has viewed the document |
signing | A field is being filled |
signed | Signing completed |
declined | Signer declined |
error | An error occurred |
useKoralaSignerRef
Programmatically control the signing iframe.
import { KoralaSigner, useKoralaSignerRef } from '@korala/react';
function ControlledSigner({ token }: { token: string }) {
const { ref, close, getStatus } = useKoralaSignerRef();
return (
<div>
<KoralaSigner ref={ref} token={token} />
<button onClick={close}>Close signing</button>
<button onClick={getStatus}>Check status</button>
</div>
);
}Events
All events are sent from the signing iframe to the parent window via postMessage. Events are prefixed with korala:.
Event Payloads
Ready
interface ReadyEventData {
version: string; // Embed protocol version
token: string; // Signer access token
}Document Loaded
interface LoadedEventData {
documentId: string;
documentName: string;
signerName: string;
signerEmail: string;
totalFields: number;
requiredFields: number;
}Document Viewed
interface ViewedEventData {
documentId: string;
signerId: string;
viewedAt: string; // ISO 8601
}Field Filled
interface FieldFilledEventData {
fieldId: string;
fieldType: string; // 'signature' | 'initials' | 'date' | 'text' | 'checkbox'
filledFields: number;
totalRequiredFields: number;
}Document Signed
interface SignedEventData {
documentId: string;
signerId: string;
signedAt: string; // ISO 8601
redirectUrl?: string;
}Document Declined
interface DeclinedEventData {
documentId: string;
signerId: string;
reason?: string;
declinedAt: string; // ISO 8601
}Error
interface ErrorEventData {
code: string;
message: string;
recoverable: boolean;
}Type Guard
Use isKoralaEvent to filter Korala events from other postMessage traffic:
import { isKoralaEvent } from '@korala/react';
window.addEventListener('message', (event) => {
if (isKoralaEvent(event)) {
console.log('Korala event:', event.data.type);
}
});Full Example
For a complete working app with single-signer, multi-signer, and batch countersign demos, see the example repository on GitHub .
Here’s a minimal signing flow: create a document via the API, then embed the signing experience.
import { useState } from 'react';
import { KoralaSigner } from '@korala/react';
export function SigningFlow() {
const [status, setStatus] = useState<'form' | 'signing' | 'complete'>('form');
const [token, setToken] = useState<string | null>(null);
async function startSigning(name: string, email: string) {
// Call your backend to create a document and get a signing token
const res = await fetch('/api/create-signing-session', {
method: 'POST',
body: JSON.stringify({ name, email }),
});
const { signingToken } = await res.json();
setToken(signingToken);
setStatus('signing');
}
if (status === 'signing' && token) {
return (
<div style={{ height: '100vh' }}>
<KoralaSigner
token={token}
signingUrl={process.env.NEXT_PUBLIC_KORALA_URL}
style={{ width: '100%', height: '100%', border: 'none' }}
onSigned={() => setStatus('complete')}
onDeclined={(data) => {
alert(`Signing declined: ${data.reason}`);
setStatus('form');
}}
onError={(data) => {
console.error('Error:', data.message);
if (!data.recoverable) setStatus('form');
}}
/>
</div>
);
}
if (status === 'complete') {
return <p>Document signed successfully!</p>;
}
return (
<form onSubmit={(e) => {
e.preventDefault();
const fd = new FormData(e.currentTarget);
startSigning(fd.get('name') as string, fd.get('email') as string);
}}>
<input name="name" placeholder="Name" required />
<input name="email" type="email" placeholder="Email" required />
<button type="submit">Start signing</button>
</form>
);
}