Skip to content
Destyler UI Destyler UI Destyler UI

Signature

The signature component allows users to draw handwritten signatures using touch or pointer devices. The signature can be saved as an image or cleared.

Draw your signature in the area below

Sign above

0 strokes

Draw your signature in the area below

Sign above

0 strokes

Draw your signature in the area below

Sign above

0 strokes

Draw your signature in the area below

Sign above

0 strokes

Features

Install

Install the component from your command line.

Terminal window
      
        
npm install @destyler/signature @destyler/vue
Terminal window
      
        
npm install @destyler/signature @destyler/react
Terminal window
      
        
npm install @destyler/signature @destyler/svelte
Terminal window
      
        
npm install @destyler/signature @destyler/solid

Anatomy

Import all parts and piece them together.

<script setup lang="ts">
import * as signature from "@destyler/signature"
import { useMachine, normalizeProps } from "@destyler/vue"
import { computed, useId } from "vue"
const [state, send] = useMachine(signature.machine({ id: useId() }))
const api = computed(() =>
signature.connect(state.value, send, normalizeProps),
)
</script>
<template>
<div v-bind="api.getRootProps()">
<label v-bind="api.getLabelProps()"></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()"></button>
<div v-bind="api.getGuideProps()"></div>
</div>
<button v-bind="api.getClearTriggerProps()"/>
</div>
</template>
import { normalizeProps, useMachine } from '@destyler/react'
import * as signature from '@destyler/signature'
import { useId, useMemo } from 'react'
export default function Signature() {
const [state, send] = useMachine(signature.machine({ id: useId() }))
const api = useMemo(
() => signature.connect(state, send, normalizeProps),
[state, send],
)
return (
<div {...api.getRootProps()}>
<label {...api.getLabelProps()}></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()}></button>
<div {...api.getGuideProps()}></div>
</div>
<button {...api.getClearTriggerProps()}></button>
</div>
)
}
<script lang="ts">
import * as signature from "@destyler/signature"
import { useMachine, normalizeProps } from "@destyler/svelte"
const id = $props.id()
const [state, send] = useMachine(signature.machine({ id }))
const api = $derived(signature.connect(state, send, normalizeProps))
</script>
<div {...api.getRootProps()}>
<label {...api.getLabelProps()}></label>
<div {...api.getControlProps()}>
<svg {...api.getSegmentProps()} >
{#each api.paths as path, i}
<path {...api.getSegmentPathProps({ path })}/>
{/each}
{#if api.currentPath}
<path {...api.getSegmentPathProps({ path: api.currentPath })}/>
{/if}
</svg>
<button {...api.getClearTriggerProps()}></button>
<div {...api.getGuideProps()}></div>
</div>
<button {...api.getClearTriggerProps()}></button>
</div>
import * as signature from '@destyler/signature'
import { normalizeProps, useMachine } from '@destyler/solid'
import { createMemo, createUniqueId, For } from 'solid-js'
export default function Signature() {
const id = createUniqueId()
const [state, send] = useMachine(signature.machine({ id }))
const api = createMemo(() =>
signature.connect(state, send, normalizeProps),
)
return (
<div {...api().getRootProps()}>
<label {...api().getLabelProps()} ></label>
<div
{...api().getControlProps()}
>
<svg
{...api().getSegmentProps()}
>
<For each={api().paths}>
{(path, i) => (
<path {...api().getSegmentPathProps({ path })}/>
)}
</For>
{api().currentPath && (
<path {...api().getSegmentPathProps({ path: api().currentPath! })}/>
)}
</svg>
<button {...api().getClearTriggerProps()}></button>
<div {...api().getGuideProps()}>
</div>
</div>
<button {...api().getClearTriggerProps()}></button>
</div>
)
}

Listening to drawing events

The signature component emits the following events:

  • onDraw: Emitted when the user is drawing the signature.

  • onDrawEnd: Emitted when the user stops drawing the signature.

const [state, send] = useMachine(
signature.machine({
onDraw(details) {
// details => { path: string[] }
console.log("Drawing signature", details)
},
onDrawEnd(details) {
// details => { path: string[], toDataURL: () => string }
console.log("Signature drawn", details)
},
}),
)

Clearing the signature

To clear the signature, use the api.clear(), or render the clear trigger button.

<script setup lang="ts">
import * as signature from "@destyler/signature"
import { useMachine, normalizeProps } from "@destyler/vue"
import { computed, useId } from "vue"
const [state, send] = useMachine(signature.machine({ id: useId() }))
const api = computed(() =>
signature.connect(state.value, send, normalizeProps),
)
</script>
<template>
<button @click="api.clear()">clear</button>
</template>
import { normalizeProps, useMachine } from '@destyler/react'
import * as signature from '@destyler/signature'
import { useId, useMemo } from 'react'
export default function Signature() {
const [state, send] = useMachine(signature.machine({ id: useId() }))
const api = useMemo(
() => signature.connect(state, send, normalizeProps),
[state, send],
)
return (
<button onClick={() => api.clear()}>
clear
</button>
)
}
<script lang="ts">
import * as signature from "@destyler/signature"
import { useMachine, normalizeProps } from "@destyler/svelte"
const id = $props.id()
const [state, send] = useMachine(signature.machine({ id }))
const api = $derived(signature.connect(state, send, normalizeProps))
</script>
<button on:click={() => api.clear()}>
clear
</button>
import * as signature from '@destyler/signature'
import { normalizeProps, useMachine } from '@destyler/solid'
import { createMemo, createUniqueId, For } from 'solid-js'
export default function Signature() {
const id = createUniqueId()
const [state, send] = useMachine(signature.machine({ id }))
const api = createMemo(() =>
signature.connect(state, send, normalizeProps),
)
return (
<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.

<script setup lang="ts">
import * as signature from "@destyler/signature"
import { useMachine, normalizeProps } from "@destyler/vue"
import { computed, useId, ref } from "vue"
const imageURL = ref<string | null>(null)
const [state, send] = useMachine(signature.machine({
id: useId(),
onDrawEnd(details) {
details.getDataUrl("image/png").then((url) => {
// set the image URL in local state
imageURL.value = url
})
},
}))
const api = computed(() =>
signature.connect(state.value, send, normalizeProps),
)
</script>
<template>
<img :src="imageURL" alt="Signature" />
</template>
import * as signature from '@destyler/signature'
import { normalizeProps, useMachine } from '@destyler/react'
import { useId, useMemo, useState } from 'react'
export default function Signature() {
const [imageURL, setImageURL] = useState<string | null>(null)
const [state, send] = useMachine(signature.machine({
id: useId(),
onDrawEnd(details) {
details.getDataUrl("image/png").then((url) => {
// set the image URL in local state
setImageURL(url)
})
},
}))
const api = useMemo(
() => signature.connect(state, send, normalizeProps),
[state, send],
)
return (
<img src={imageURL} alt="Signature" />
)
}
<script lang="ts">
import * as signature from "@destyler/signature"
import { useMachine, normalizeProps } from "@destyler/svelte"
let imageURL: string | null = null
const id = $props.id()
const [state, send] = useMachine(signature.machine({
id,
onDrawEnd(details) {
details.getDataUrl("image/png").then((url) => {
// set the image URL in local state
imageURL = url
})
},
}))
const api = $derived(signature.connect(state, send, normalizeProps))
</script>
<img src={imageURL} alt="Signature" />
import * as signature from '@destyler/signature'
import { normalizeProps, useMachine } from '@destyler/solid'
import { createMemo, createUniqueId, createSignal } from 'solid-js'
export default function Signature() {
const id = createUniqueId()
const [imageURL, setImageURL] = createSignal<string | null>(null)
const [state, send] = useMachine(signature.machine({
id,
onDrawEnd(details) {
details.getDataUrl("image/png").then((url) => {
// set the image URL in local state
imageURL = url
})
},
}))
const api = createMemo(() =>
signature.connect(state, send, normalizeProps),
)
return (
<img src={imageURL} alt="Signature" />
)
}

Changing the stroke color

To change the stroke color, set the drawing.fill option to a valid CSS color.

const [state, send] = useMachine(
signature.machine({
drawing: {
fill: "blue",
},
}),
)

Changing the stroke width

To change the stroke width, set the drawing.size option to a number.

const [state, send] = useMachine(
signature.machine({
drawing: {
size: 5,
},
}),
)

Simulating pressure sensitivity

Pressure sensitivity is disabled by default. To enable it, set the drawing.simulatePressure option to true.

const [state, send] = useMachine(
signature.machine({
drawing: {
simulatePressure: true,
},
}),
)

Using in form

To use the signature pad in a form, set the name context property.

<script setup lang="ts">
import * as signature from "@destyler/signature"
import { useMachine, normalizeProps } from "@destyler/vue"
import { computed, useId, ref } from "vue"
const imageURL = ref<string | null>(null)
const [state, send] = useMachine(signature.machine({
id: useId(),
name: "signature",
onDrawEnd(details) {
details.getDataUrl("image/png").then((url) => {
// set the image URL in local state
imageURL.value = url
})
},
}))
const api = computed(() =>
signature.connect(state.value, send, normalizeProps),
)
</script>
<template>
<input v-bind="api.getHiddenInputProps({ value: imageURL })" />
</template>
import * as signature from '@destyler/signature'
import { normalizeProps, useMachine } from '@destyler/react'
import { useId, useMemo, useState } from 'react'
export default function Signature() {
const [imageURL, setImageURL] = useState<string | null>(null)
const [state, send] = useMachine(signature.machine({
id: useId(),
name: "signature",
onDrawEnd(details) {
details.getDataUrl("image/png").then((url) => {
// set the image URL in local state
setImageURL(url)
})
},
}))
const api = useMemo(
() => signature.connect(state, send, normalizeProps),
[state, send],
)
return (
<input {...api.getHiddenInputProps({ value: imageURL })} />
)
}
<script lang="ts">
import * as signature from "@destyler/signature"
import { useMachine, normalizeProps } from "@destyler/svelte"
let imageURL: string | null = null
const id = $props.id()
const [state, send] = useMachine(signature.machine({
id,
name: "signature",
onDrawEnd(details) {
details.getDataUrl("image/png").then((url) => {
// set the image URL in local state
imageURL = url
})
},
}))
const api = $derived(signature.connect(state, send, normalizeProps))
</script>
<input {...api.getHiddenInputProps({ value: imageURL })} />
import * as signature from '@destyler/signature'
import { normalizeProps, useMachine } from '@destyler/solid'
import { createMemo, createUniqueId, createSignal } from 'solid-js'
export default function Signature() {
const id = createUniqueId()
const [imageURL, setImageURL] = createSignal<string | null>(null)
const [state, send] = useMachine(signature.machine({
id,
onDrawEnd(details) {
details.getDataUrl("image/png").then((url) => {
// set the image URL in local state
imageURL = url
})
},
}))
const api = createMemo(() =>
signature.connect(state, send, normalizeProps),
)
return (
<input {...api().getHiddenInputProps({ value: imageURL })} />
)
}

Disabling the signature

Set the disabled context property to true to disable the signature.

const [state, send] = useMachine(
signature.machine({
disabled: true,
}),
)

Make the signature read only

Set the readOnly context property to true to make the signature pad read only.

const [state, send] = useMachine(
signature.machine({
readOnly: true,
}),
)

Methods and Properties

Machine Context

The signature machine exposes the following context properties:

ids
Partial<{ root: string; control: string; hiddenInput: string; label: string; }>
The ids of the signature elements. Useful for composition.
translations
IntlTranslations
The translations of the signature. Useful for internationalization.
onDraw
(details: DrawDetails) => void
Callback when the signature is drawing.
onDrawEnd
(details: DrawEndDetails) => void
Callback when the signature is done drawing.
drawing
DrawingOptions
The drawing options.
disabled
boolean
Whether the signature is disabled.
required
boolean
Whether the signature is required.
readOnly
boolean
Whether the signature is read-only.
name
string
The name of the signature. Useful for form submission.
dir
"ltr" | "rtl"
The document's text/writing direction.
id
string
The unique identifier of the machine.
getRootNode
() => ShadowRoot | Node | Document
A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.

Machine API

The signature api exposes the following methods:

empty
boolean
Whether the signature is empty.
drawing
boolean
Whether the user is currently drawing.
currentPath
string
The current path being drawn.
paths
string[]
The paths of the signature.
getDataUrl
(type: DataUrlType, quality?: number) => Promise<string>
Returns the data URL of the signature.
clear
() => void
Clears the signature.

Data Attributes

Root

name
desc
data-scope
signature
data-part
root
data-disabled
Present when disabled

Label

name
desc
data-scope
signature
data-part
label
data-disabled
Present when disabled

Control

name
desc
data-scope
signature
data-part
control
data-disabled
Present when disabled

Guide

name
desc
data-scope
signature
data-part
guide
data-disabled
Present when disabled