Signature Pad
A signature pad lets users draw handwritten signatures with pointer or touch input.
Features
- Draw signatures using touch or pointer input
- Export signatures as data URLs
- Clear signatures programmatically
Installation
Install the signature pad package:
npm install @zag-js/signature-pad @zag-js/react # or yarn add @zag-js/signature-pad @zag-js/react
npm install @zag-js/signature-pad @zag-js/solid # or yarn add @zag-js/signature-pad @zag-js/solid
npm install @zag-js/signature-pad @zag-js/vue # or yarn add @zag-js/signature-pad @zag-js/vue
npm install @zag-js/signature-pad @zag-js/svelte # or yarn add @zag-js/signature-pad @zag-js/svelte
Anatomy
To set up the signature pad correctly, you'll need to understand its anatomy and how we name its parts.
Each part includes a
data-partattribute to help identify them in the DOM.
Usage
Import the signature pad package:
import * as signaturePad from "@zag-js/signature-pad"
The signature pad package exports two key functions:
machine- State machine logic.connect- Maps machine state to JSX props and event handlers.
Then use the framework integration helpers:
import { normalizeProps, useMachine } from "@zag-js/react" import * as signaturePad from "@zag-js/signature-pad" import { useId, useState } from "react" export function SignaturePad() { const service = useMachine(signaturePad.machine, { id: useId(), }) const api = signaturePad.connect(service, normalizeProps) return ( <div {...api.getRootProps()}> <label {...api.getLabelProps()}>Signature Pad</label> <div {...api.getControlProps()}> <svg {...api.getSegmentProps()}> {api.paths.map((path, i) => ( <path key={i} {...api.getSegmentPathProps({ path })} /> ))} {api.currentPath && ( <path {...api.getSegmentPathProps({ path: api.currentPath })} /> )} </svg> <button {...api.getClearTriggerProps()}>X</button> <div {...api.getGuideProps()} /> </div> </div> ) }
import * as signaturePad from "@zag-js/signature-pad" import { useMachine, normalizeProps } from "@zag-js/solid" import { createMemo, createUniqueId } from "solid-js" export function SignaturePad() { const service = useMachine(signaturePad.machine, { id: createUniqueId(), }) const api = createMemo(() => signaturePad.connect(service, normalizeProps)) return ( <div {...api().getRootProps()}> <label {...api().getLabelProps()}>Signature Pad</label> <div {...api().getControlProps()}> <svg {...api().getSegmentProps()}> {api().paths.map((path, i) => ( <path key={i} {...api().getSegmentPathProps({ path })} /> ))} {api().currentPath && ( <path {...api().getSegmentPathProps({ path: api().currentPath })} /> )} </svg> <button {...api().getClearTriggerProps()}>X</button> <div {...api().getGuideProps()} /> </div> </div> ) }
<script setup> import * as signaturePad from "@zag-js/signature-pad" import { useMachine, normalizeProps } from "@zag-js/vue" import { computed } from "vue" const service = useMachine(signaturePad.machine, { id: "1" }) const api = computed(() => signaturePad.connect(service, normalizeProps)) </script> <template> <div v-bind="api.getRootProps()"> <label v-bind="api.getLabelProps()">Signature Pad</label> <div v-bind="api.getControlProps()"> <svg v-bind="api.getSegmentProps()"> <path v-for="(path, i) of api.paths" :key="i" v-bind="api.getSegmentPathProps({ path })" /> <path v-if="api.currentPath" v-bind="api.getSegmentPathProps({ path: api.currentPath })" /> </svg> <button v-bind="api.getClearTriggerProps()">X</button> <div v-bind="api.getGuideProps()" /> </div> </div> </template>
<script lang="ts"> import * as signaturePad from "@zag-js/signature-pad" import { normalizeProps, useMachine } from "@zag-js/svelte" const id = $props.id() const service = useMachine(signaturePad.machine, { id }) const api = $derived(signaturePad.connect(service, normalizeProps)) </script> <div {...api.getRootProps()}> <label {...api.getLabelProps()}>Signature Pad</label> <div {...api.getControlProps()}> <svg {...api.getSegmentProps()}> {#each api.paths as path} <path {...api.getSegmentPathProps({ path })} /> {/each} {#if api.currentPath} <path {...api.getSegmentPathProps({ path: api.currentPath })} /> {/if} </svg> <button {...api.getClearTriggerProps()}>X</button> <div {...api.getGuideProps()}></div> </div> </div>
Listening to drawing events
The signature pad component emits the following events:
onDraw: Emitted when the user is drawing the signature.onDrawEnd: Emitted when the user stops drawing the signature.
const service = useMachine(signaturePad.machine, { onDraw(details) { // details => { paths: string[] } console.log("Drawing signature", details) }, onDrawEnd(details) { // details => { paths: string[], getDataUrl: (type, quality?) => Promise<string> } console.log("Signature drawn", details) }, })
Clearing the signature
To clear the signature, use the api.clear(), or render the clear trigger
button.
<button onClick={() => api.clear()}>Clear</button>
Rendering an image preview
Use the api.getDataUrl() method to get the signature as a data URL and render
it as an image.
You can also leverage the
onDrawEndevent to get the signature data URL.
const service = useMachine(signaturePad.machine, { onDrawEnd(details) { details.getDataUrl("image/png").then((url) => { // set the image URL in local state setImageURL(url) }) }, })
Next, render the image preview using the URL.
<img src={imageURL} alt="Signature" />
Controlled signature paths
Use paths and onDraw for controlled usage.
const service = useMachine(signaturePad.machine, { paths, onDraw(details) { setPaths(details.paths) }, })
Changing the stroke color
To change the stroke color, set the drawing.fill option to a valid CSS color.
Note: You can't use a css variable as the stroke color.
const service = useMachine(signaturePad.machine, { drawing: { fill: "red", }, })
Changing the stroke width
To change the stroke width, set the drawing.size option to a number.
const service = useMachine(signaturePad.machine, { drawing: { size: 5, }, })
Controlling pressure simulation
Pressure simulation is enabled by default. Set drawing.simulatePressure to
false to use raw pointer pressure values.
const service = useMachine(signaturePad.machine, { drawing: { simulatePressure: false, }, })
Usage in forms
To use the signature pad in a form, set the name context property.
const service = useMachine(signaturePad.machine, { name: "signature", })
Then, render the hidden input element using api.getHiddenInputProps().
<input {...api.getHiddenInputProps({ value: imageURL })} />
Customizing screen reader text
Use translations to customize accessible text for the drawing control and
clear trigger.
const service = useMachine(signaturePad.machine, { translations: { control: "Signature drawing area", clearTrigger: "Clear signature", }, })
Methods and Properties
The signature pad api exposes the following methods and properties:
Machine Context
The signature pad machine exposes the following context properties:
idsPartial<{ root: string; control: string; hiddenInput: string; label: string; }>The ids of the signature pad elements. Useful for composition.translationsIntlTranslationsThe translations of the signature pad. Useful for internationalization.onDraw(details: DrawDetails) => voidCallback when the signature pad is drawing.onDrawEnd(details: DrawEndDetails) => voidCallback when the signature pad is done drawing.drawingDrawingOptionsThe drawing options.disabledbooleanWhether the signature pad is disabled.requiredbooleanWhether the signature pad is required.readOnlybooleanWhether the signature pad is read-only.namestringThe name of the signature pad. Useful for form submission.defaultPathsstring[]The default paths of the signature pad. Use when you don't need to control the paths of the signature pad.pathsstring[]The controlled paths of the signature pad.dir"ltr" | "rtl"The document's text/writing direction.idstringThe unique identifier of the machine.getRootNode() => ShadowRoot | Node | DocumentA root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.
Machine API
The signature pad api exposes the following methods:
emptybooleanWhether the signature pad is empty.drawingbooleanWhether the user is currently drawing.currentPathstringThe current path being drawn.pathsstring[]The paths of the signature pad.getDataUrl(type: DataUrlType, quality?: number) => Promise<string>Returns the data URL of the signature pad.clearVoidFunctionClears the signature pad.