Skip to Content
SDKsReact SDK

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/react

Quick 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

PropTypeRequiredDescription
tokenstringYesSigner access token (from the API)
signingUrlstringNoBase URL of the signing app. Defaults to https://korala.ai
classNamestringNoCSS class for the iframe
styleCSSPropertiesNoInline styles for the iframe
allowedOriginsstring[]NoRestrict which origins can send events. Accepts all if omitted
themeKoralaSignerThemeNoCustomize accent color, border radius, and font
onReady(data) => voidNoIframe initialized
onLoaded(data) => voidNoDocument loaded in viewer
onViewed(data) => voidNoSigner viewed the document
onFieldFilled(data) => voidNoA field was filled
onSigned(data) => voidNoSigner completed signing
onDeclined(data) => voidNoSigner declined to sign
onError(data) => voidNoAn 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

PropertyTypeDefaultDescription
accentColorstring#D4503CHex color for buttons, borders, and focus states
borderRadiusnumber0Border radius in pixels (clamped 0-24) for buttons, modals, and inputs
fontFamilystringinheritCSS 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):

Default signing theme

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

Blue custom theme

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

PropTypeDefaultDescription
widthnumber600Canvas width in pixels
heightnumber200Canvas height in pixels
penColorstring#000000Stroke color
classNamestringCSS class for the container
styleCSSPropertiesInline styles
onSignatureCreated(data) => voidCalled with { imageDataUrl } (base64 PNG) when a stroke ends
onClear() => voidCalled 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

StatusDescription
loadingInitial state, waiting for iframe
readyIframe initialized
loadedDocument loaded in viewer
viewedSigner has viewed the document
signingA field is being filled
signedSigning completed
declinedSigner declined
errorAn 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> ); }
Last updated on