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

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.

You can also leverage the onDrawEnd event to get the signature data URL.

<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.

You can’t use a css variable as the stroke 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 pad elements. Useful for composition.
translations
IntlTranslations
The translations of the signature pad. Useful for internationalization.
onDraw
(details: DrawDetails) => void
Callback when the signature pad is drawing.
onDrawEnd
(details: DrawEndDetails) => void
Callback when the signature pad is done drawing.
drawing(default: '{ size: 2, simulatePressure: true }')
DrawingOptions
The drawing options.
disabled
boolean
Whether the signature pad is disabled.
required
boolean
Whether the signature pad is required.
readOnly
boolean
Whether the signature pad is read-only.
name
string
The name of the signature pad. Useful for form submission.
dir(default: "ltr")
"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 pad is empty.
drawing
boolean
Whether the user is currently drawing.
currentPath
string
The current path being drawn.
paths
string[]
The paths of the signature pad.
getDataUrl
(type: DataUrlType, quality?: number) => Promise<string>
Returns the data URL of the signature pad.
clear
() => void
Clears the signature pad.

Data Attributes

Root

attribute
description
data-scope
signature
data-part
root
data-disabled
Present when disabled

Label

attribute
description
data-scope
signature
data-part
label
data-disabled
Present when disabled

Control

attribute
description
data-scope
signature
data-part
control
data-disabled
Present when disabled

Guide

attribute
description
data-scope
signature
data-part
guide
data-disabled
Present when disabled