Draw, type, or upload a signature, keep a reusable library, and click a page to place one.
"use client";
import { createPluginRegistration } from "@embedpdf/core";About#
The front end for embedpdf's signature plugin, which owns the whole workflow — capturing a signature, keeping a reusable library of them, and rendering a placed one through the annotation layer. This ships as one component with its parts exported:
<PdfSignatureCreator />— draw, type, or upload, built on embedpdf's own headless pads:<SignatureDrawPad />,<SignatureTypePad />, and theuseSignatureUploadhook. Saving adds the result to the library via the signature capability'saddEntry.<PdfSignatureLibrary />— the saved signatures. Picking one arms placement (forDocument(documentId).activateSignaturePlacement(entryId)); the next click on a page drops it in.<PdfSignatureManager />— the two stacked, with no container of its own.<PdfSignatureDialog />— the manager in a modal, because the next click belongs to the page.<PdfSignatureButton />— the toolbar toggle that drives the dialog. It stays pressed while a placement is armed, and clicking it then cancels.
Every part reads its own scope off the plugin store rather than through a provider of ours, so any of them mounts alone — a settings page can render the creator with no placement flow around it.
Placed signatures render through the annotation plugin's <PdfAnnotationLayer>
— the signature plugin registers its renderers there — so both plugins have to
be registered and a layer rendered.
Every surface that holds a signature — the two pads, each card in the saved
library — is paper-coloured in both schemes. The plugin bakes the ink colour
into what it hands back, and that ink is black so it survives being stamped on a
page, so the surface under it has to stay white too. They all read --pdf-page;
see Recolouring the paper.
The upload dropzone is the exception until an image lands on it — an empty
dropzone is app chrome, not a signature.
This renders a disabled shell until both AnnotationPluginPackage and
SignaturePluginPackage are registered on the viewer. The signature plugin
requires the annotation plugin — registering it alone won't place anything.
Installation#
pnpm dlx shadcn@latest add @pdfviewcn/signature
Usage#
Register the annotation and signature plugins.
import { createPluginRegistration } from "@embedpdf/core";
import { AnnotationPluginPackage } from "@embedpdf/plugin-annotation/react";
import { SignaturePluginPackage } from "@embedpdf/plugin-signature/react";
// Outside the component — a new array identity on every render tears the
// engine down and rebuilds it, losing scroll and zoom state.
const plugins = [
createPluginRegistration(AnnotationPluginPackage),
createPluginRegistration(SignaturePluginPackage),
];Render the annotation layer — placed signatures render through it — and drop the button in the toolbar.
<PdfViewer
documents={[{ url: "/sample.pdf" }]}
plugins={plugins}
className="h-[720px]"
>
<PdfToolbar>
<PdfSignatureButton />
</PdfToolbar>
<PdfViewerContent
pageLayers={({ documentId, pageIndex }) => (
<PdfAnnotationLayer documentId={documentId} pageIndex={pageIndex} />
)}
/>
</PdfViewer>Examples#
Default#
Open the button, create a signature by drawing, typing, or uploading an image, and save it. Pick a saved signature to arm placement, then click a page to drop it in. Click the button again while it's pressed to cancel placement.
"use client";
import { createPluginRegistration } from "@embedpdf/core";Your own trigger#
<PdfSignatureDialog /> takes a trigger through children, so the toolbar
toggle isn't the only way in.
<PdfSignatureDialog>
<DialogTrigger render={<Button variant="outline" />}>
Add signature
</DialogTrigger>
</PdfSignatureDialog>Your own container#
The dialog is a convenience, not a requirement. Mount
<PdfSignatureManager /> in a popover, a sheet, or a sidebar panel instead —
it brings no container of its own.
<Popover>
<PopoverTrigger render={<Button variant="outline" />}>
Signature
</PopoverTrigger>
<PopoverContent className="w-80">
<PdfSignatureManager />
</PopoverContent>
</Popover>Creator on its own#
A settings page has no page to click, so it wants the creator without the library or the placement flow. Saving still writes to the plugin's library, so signatures made here show up in the viewer later.
<PdfSignatureCreator defaultValue="type" />Accessibility#
The trigger is a single toggle button labelled "Signature", with a tooltip to match. Its pressed state reflects whether a placement is armed, and the tooltip switches to explain that clicking a page places the signature — or clicking the button cancels.
Placing a signature requires a pointer, since the drop point is chosen by clicking a page; there's no keyboard equivalent for picking a spot. Creation is keyboard reachable: the upload dropzone is a real button, so it takes focus and opens the file picker on Enter or Space, and each saved signature is a button named by its label.
API Reference#
PdfSignatureButton#
A <Toggle /> carrying data-slot="pdf-signature-button", plus the dialog it
drives. Accepts every prop the underlying toggle takes; pressed,
onPressedChange, aria-label, and the default icon are managed internally but
can be overridden.
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | — | Whether the signature dialog is open. |
defaultOpen | boolean | false | Initial open state. Uncontrolled. |
onOpenChange | (open: boolean, eventDetails?) => void | — | Called when the dialog opens or closes. |
The button reads the signature capability through useSignatureCapability() and
the armed placement through useActivePlacement(documentId). It renders
disabled whenever there's no active document or the signature plugin isn't
registered.
PdfSignatureDialog#
<PdfSignatureManager /> in a modal, because the next click belongs to the page
rather than to the dialog. Picking a signature arms placement and closes.
Accepts every <Dialog /> prop; children is the trigger slot.
onOpenChange carries Base UI's eventDetails as an optional second argument —
optional because closing on a pick happens from the inside, where there's no DOM
event to attribute the change to.
PdfSignatureManager#
<PdfSignatureLibrary /> stacked over <PdfSignatureCreator /> in a plain
<div> carrying data-slot="pdf-signature-manager". Forwards onSelect and
onSave to its two parts.
PdfSignatureCreator#
The draw/type/upload tabs, carrying data-slot="pdf-signature-creator". Accepts
every <Tabs /> prop, so defaultValue picks the starting tab.
| Prop | Type | Default | Description |
|---|---|---|---|
onSave | (definition: SignatureFieldDefinition) => void | — | Replaces the default, which saves the signature to the plugin's library. |
The Type tab's field isn't a preview of the result. The pad renders the artifact on an offscreen canvas at 48px, centred and cropped to the ink, and shows you a plain text input capped at 32px — the two aren't decoupled upstream.
PdfSignatureLibrary#
The saved signatures as a pick-to-place grid, carrying
data-slot="pdf-signature-library". Renders nothing until there's at least one
saved signature.
| Prop | Type | Default | Description |
|---|---|---|---|
onSelect | (entryId: string) => void | — | Replaces the default, which arms placement for the picked signature. |
onRemove | (entryId: string) => void | — | Replaces the default, which deletes the entry from the plugin's library. |