React Components

inklayer-react provides two top-level components: PdfAnnotator and PdfViewer. Built on React 18+, using Zustand for state management, with ThemeProvider and i18n support.

Installation

npm install inklayer-react

Basic Import

import { PdfAnnotator, PdfViewer } from 'inklayer-react'
import 'inklayer-react/style'

// Types
import type { PdfAnnotatorProps, PdfViewerProps, User, IAnnotationStore } from 'inklayer-react'

PdfAnnotator

Full-featured PDF annotation component with built-in toolbar, annotation editing, sidebar, and save/load/export capabilities.

Props

PropTypeDefaultDescription
urlstring | URLPDF file URL. Mutually exclusive with data
datastring | number[] | ArrayBuffer | Uint8Array | Uint16Array | Uint32ArrayPDF binary data. Mutually exclusive with url
appearance'auto' | 'dark' | 'light''auto'UI appearance mode. auto follows system theme
themeSee theme color table below'violet'UI theme color
titleReact.ReactNode'PDF ANNOTATOR'Annotator title
locale'zh-CN' | 'en-US''zh-CN'UI language
initialScale'auto' | 'page-actual' | 'page-fit' | 'page-width' | number'auto'Initial zoom scale. 'page-actual' = actual size, 'page-fit' = fit page, 'page-width' = page width mode
layoutStyleReact.CSSPropertiesundefinedContainer styles. Must be explicitly provided, otherwise component height is 0. See note below
user{ id: string, name: string }{ id: 'null', name: 'unknown' }Current user info for annotation ownership
enableNativeAnnotationsbooleanfalseEnable PDF.js native annotation rendering
enableRangeboolean | 'auto''auto'PDF range loading mode. 'auto' auto-detects
defaultOptionsDeepPartial<PdfAnnotatorOptions>Default annotation options (colors, signature, stamp)
initialAnnotationsAnnotation[][]Initial annotation data for restoring saved annotations
defaultShowAnnotationsSidebarbooleanfalseWhether annotation sidebar is expanded by default
actionsReactNode | ((props: ActionsProps) => ReactNode)Custom actions area, rendered on the right side of toolbar. Supports function form receiving operation context

Theme Color Options

Color ValueDescription
grayGray
goldGold
bronzeBronze
brownBrown
yellowYellow
amberAmber
orangeOrange
tomatoTomato
redRed
rubyRuby
crimsonCrimson
pinkPink
plumPlum
purplePurple
violetViolet (default)
irisIris
indigoIndigo
blueBlue
cyanCyan
tealTeal
jadeJade
greenGreen
grassGrass
limeLime
mintMint
skySky

Event Callbacks

EventSignatureTrigger
onSave(annotations: Annotation[]) => voidUser clicks save button
onLoad() => voidPDF loading completed
onAnnotationAdded(annotation: Annotation) => voidNew annotation created
onAnnotationDeleted(id: string) => voidAnnotation deleted
onAnnotationSelected(annotation: Annotation | null, isClick: boolean) => voidAnnotation selected/deselected
onAnnotationUpdated(annotation: Annotation) => voidAnnotation updated (moved/edited)

Full Example: Annotator with Persistence

import { PdfAnnotator } from 'inklayer-react'
import 'inklayer-react/style'
import type { Annotation } from 'inklayer-react'
import { useState, useCallback } from 'react'

export default function AnnotatorPage() {
  const [annotations, setAnnotations] = useState<Annotation[]>([])

  const handleSave = useCallback((annotations: Annotation[]) => {
    setAnnotations(annotations)
    // Send annotation data to backend
    fetch('/api/annotations', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(annotations),
    })
  }, [])

  return (
    <PdfAnnotator
      url="/document.pdf"
      locale="en-US"
      layoutStyle={{ height: '96vh' }}
      enableRange="auto"
      defaultShowAnnotationsSidebar={true}
      user={{ id: 'editor-1', name: 'Alice' }}
      initialAnnotations={annotations}
      defaultOptions={{
        colors: ['#FF0000', '#00FF00', '#0000FF'],
        signature: {
          defaultSignature: [],
          defaultFont: [{ label: 'Kai', value: 'STKaiti', external: false }]
        },
        stamp: { defaultStamp: [] }
      }}
      onSave={handleSave}
      onLoad={() => console.log('PDF loaded')}
      onAnnotationAdded={(a) => console.log('Added:', a.id)}
      onAnnotationDeleted={(id) => console.log('Deleted:', id)}
      actions={(props) => (
        <>
          <button onClick={() => props.save()}>Save</button>
          <button onClick={() => console.log(props.getAnnotations())}>Get Annotations</button>
          <button onClick={() => props.exportToExcel('export')}>Export Excel</button>
          <button onClick={() => props.exportToPdf('export')}>Export PDF</button>
        </>
      )}
    />
  )
}

⚠️ About layoutStyle

PdfAnnotator and PdfViewer use absolute positioning internally. The component size is entirely controlled by layoutStyleit does not auto-fit to the parent container’s height. layoutStyle is a standard React.CSSProperties object, but all values must be passed as strings with CSS units:

// ✅ Correct
<PdfAnnotator layoutStyle={{ width: '100%', height: '600px' }} ... />
<PdfAnnotator layoutStyle={{ width: '100vw', height: '100vh' }} ... />

// ❌ Wrong (numeric values without units — the component can't calculate dimensions)
<PdfAnnotator layoutStyle={{ width: 800, height: 600 }} ... />

Common layout patterns:

// Pattern A: Full viewport
<PdfAnnotator layoutStyle={{ width: '100vw', height: '100vh' }} ... />

// Pattern B: Inside a container with fixed height
<div style={{ height: '600px' }}>
  <PdfAnnotator layoutStyle={{ width: '100%', height: '100%' }} ... />
</div>

// Pattern C: Minus top navbar height
<PdfAnnotator layoutStyle={{ width: '100%', height: 'calc(100vh - 64px)' }} ... />

If the page is blank, the first thing to check: open React DevTools and inspect whether the PdfAnnotator DOM element has a computed height.

About onSave

onSave fires when the user clicks the “Save” button on the right side of the toolbar. The callback receives annotations in the complete Annotation[] format — save this array directly, no conversion needed:

const handleSave = (annotations: Annotation[]) => {
  // annotations is already complete data, send directly
  fetch('/api/annotations', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(annotations),
  })
}

Backend storage tip: If you need to structurally store annotation data on the backend, let the backend decide the storage format. The frontend only needs to get the complete Annotation[] via onSave and send it.

PdfViewer

Lightweight PDF viewer without annotation editing. Ideal for read-only PDF viewing.

Props (inherits PdfAnnotator base props, plus the following)

PropTypeDefaultDescription
showTextLayerbooleantrueShow text selection layer
showAnnotationsbooleanfalseShow existing annotations
defaultActiveSidebarKeystring | nullnullDefault expanded sidebar panel key
toolbarReactNode | (ctx) => ReactNodeCustom toolbar (render props supported)
sidebarSidebarPanel[]Custom sidebar panels

Viewer Full Example

import { PdfViewer } from 'inklayer-react'
import 'inklayer-react/style'

export default function ViewerPage() {
  return (
    <PdfViewer
      url="/document.pdf"
      showTextLayer={true}
      showAnnotations={true}
      layoutStyle={{ height: '96vh' }}
      enableRange="auto"
      defaultActiveSidebarKey={null}
      actions={(context) => (
        <>
          <button onClick={() => context.print()}>Print</button>
          <button onClick={() => context.download('file')}>Download</button>
          <button onClick={context.toggleSidebar}>Toggle Sidebar</button>
        </>
      )}
      toolbar={(context) => (
        <div>
          <button onClick={() => context.pdfViewer?.scrollPageIntoView({ pageNumber: 1 })}>
            Go to Page 1
          </button>
        </div>
      )}
      sidebar={[{
        key: 'custom-sidebar',
        title: 'Custom Panel',
        icon: <span>🔧</span>,
        render: (context) => (
          <div>
            <button onClick={context.toggleSidebar}>Close</button>
          </div>
        )
      }]}
      onDocumentLoaded={(pdfViewer) => console.log('PDF loaded:', pdfViewer)}
      onEventBusReady={(eventBus) => {
        eventBus?.on('pagerendered', (e) => console.log('Page:', e.pageNumber))
      }}
    />
  )
}

SidebarPanel Type

The sidebar prop accepts SidebarPanel[], defining custom sidebar panels:

interface SidebarPanel {
  key: string                    // Panel unique key
  title: React.ReactNode        // Panel title
  icon: React.ReactNode         // Panel icon
  render: (context: PdfViewerContextValue) => React.ReactNode  // Panel content render function
}

PdfViewerContextValue Type

The function form of actions, toolbar, sidebar receives a PdfViewerContextValue object:

interface PdfViewerContextValue {
  pdfViewer: PDFViewer | null    // PDFViewer instance
  currentPage: number           // Current page number (0-based)
  totalPages: number            // Total pages
  scale: number                 // Current zoom scale
  print: () => void           // Print
  download: (filename?: string) => void  // Download
  toggleSidebar: () => void   // Toggle sidebar
  scrollPageIntoView: (opts: { pageNumber: number }) => void  // Scroll to page
}

ActionsProps Type

The actions prop can accept a function component that receives an ActionsProps object:

interface ActionsProps {
  save: () => void                          // Trigger onSave callback
  getAnnotations: () => Annotation[]        // Get all current annotations
  exportToExcel: (fileName?: string) => void // Export to Excel
  exportToPdf: (fileName?: string) => void    // Export to PDF
}

See the “Full Example” section above for usage.

PdfAnnotatorOptions Configuration

Customize default annotation behavior via the defaultOptions prop:

interface PdfAnnotatorOptions {
  colors?: string[]  // Available color list

  signature?: {
    colors?: string[]                    // Signature color options
    type?: 'Draw' | 'Enter' | 'Upload'   // Signature type (note capital first letter)
    maxSize?: number                     // Max signature image file size (bytes)
    accept?: string                      // Accepted image formats
    defaultSignature?: string[]          // Default signature list
    defaultFont?: {                      // Default font list
      label: string
      value: string
      external?: boolean
      url?: string                     // External font URL
    }[]
  }

  stamp?: {
    maxSize?: number                     // Stamp image max file size
    accept?: string                      // Accepted image formats
    defaultStamp?: string[]              // Default stamp list
    editor?: {                           // Stamp editor config
      defaultBackgroundColor?: string
      defaultBorderColor?: string
      defaultBorderStyle?: 'none' | 'solid' | 'dashed'
      defaultTextColor?: string
      defaultFont?: {
        label: string
        value: string
      }[]
    }
  }
}

Note: signature.type values are 'Draw' | 'Enter' | 'Upload' (capitalized), NOT 'draw' | 'text' | 'image'.