@letar/forms

Signature

Digital signature field with canvas drawing and typed mode

Overview

Form.Field.Signature provides a digital signature input for legal forms, contracts, and consent documents. Users can draw with mouse/finger or type their name in cursive font.

Full example

The complete sandbox example, read directly from the form-develop-app / form-develop-app-shadcn source at build time. The shadcn side (FieldSignature) lives inside the broader "specialized fields" demo, alongside Editable, ColorPicker, and FileUpload — not as a standalone page.

apps/form-develop-app/src/app/signature-demo/page.tsx
'use client'

import { Code, Heading, Image, Text, VStack } from '@chakra-ui/react'
import { Form } from '@letar/forms'
import { useState } from 'react'
import { z } from 'zod/v4'
import { DemoPageLayout } from '../_components'

const ContractSchema = z
  .object({
    name: z
      .string()
      .min(1, 'Required')
      .meta({ ui: { title: 'Full Name' } }),
    signature: z.string().min(1, 'Signature is required'),
  })
  .strip()

export default function SignatureDemoPage() {
  const [result, setResult] = useState<Record<string, unknown> | null>(null)

  return (
    <DemoPageLayout title="Signature Field" description="Form.Field.Signature — canvas drawing + typed mode">
      {/* Draw mode (по умолчанию) */}
      <VStack gap={4} align="stretch">
        <Heading size="lg">1. Draw + Typed Mode</Heading>
        <Text color="fg.muted">Рисуйте мышью/пальцем или введите имя для курсивной подписи.</Text>

        <Form initialValue={{ name: '', signature: '' }} schema={ContractSchema} onSubmit={(data) => setResult(data)}>
          <Form.Field.String name="name" label="Full Name" />
          <Form.Field.Signature name="signature" label="Your Signature" placeholder="Sign here" clearLabel="Clear" />
          <Form.Button.Submit>Sign Contract</Form.Button.Submit>
        </Form>
      </VStack>

      {/* Кастомные настройки */}
      <VStack gap={4} align="stretch" mt={8}>
        <Heading size="lg">2. Custom Style</Heading>
        <Text color="fg.muted">Синяя подпись, увеличенный canvas, толстая линия.</Text>

        <Form initialValue={{ signature: '' }} onSubmit={(data) => setResult(data)}>
          <Form.Field.Signature
            name="signature"
            label="Premium Signature"
            width={500}
            height={200}
            strokeColor="#1a365d"
            strokeWidth={3}
            backgroundColor="#f7fafc"
            placeholder="Sign with style"
          />
          <Form.Button.Submit>Submit</Form.Button.Submit>
        </Form>
      </VStack>

      {/* Draw only (без typed mode) */}
      <VStack gap={4} align="stretch" mt={8}>
        <Heading size="lg">3. Draw Only (no typed)</Heading>

        <Form initialValue={{ signature: '' }} onSubmit={(data) => setResult(data)}>
          <Form.Field.Signature
            name="signature"
            label="Handwritten Only"
            allowTyped={false}
            placeholder="Draw your signature"
          />
          <Form.Button.Submit>Submit</Form.Button.Submit>
        </Form>
      </VStack>

      {/* Результат */}
      {result && (
        <VStack gap={2} align="stretch" mt={8}>
          <Heading size="md">Submitted Data</Heading>
          {typeof result.signature === 'string' && result.signature.startsWith('data:') && (
            <Image
              src={result.signature as string}
              alt="Signature"
              border="1px solid"
              borderColor="border"
              borderRadius="md"
              maxW="400px"
            />
          )}
          <Code whiteSpace="pre" maxH="200px" overflow="auto">
            {JSON.stringify(
              { ...result, signature: result.signature ? `${(result.signature as string).slice(0, 50)}...` : '' },
              null,
              2,
            )}
          </Code>
        </VStack>
      )}
    </DemoPageLayout>
  )
}

Basic Usage

<Form initialValue={{ signature: '' }} onSubmit={handleSubmit}>
  <Form.Field.Signature name="signature" label="Your Signature" />
  <Form.Button.Submit>Sign</Form.Button.Submit>
</Form>

The field value is a data URI string (image/png base64), suitable for storage in a database or submission to an API.

Draw Mode

The default mode. Users draw directly on the canvas with mouse or touch.

<Form.Field.Signature
  name="signature"
  label="Signature"
  width={400}
  height={150}
  strokeColor="black"
  strokeWidth={2}
  placeholder="Sign here"
  clearLabel="Clear"
/>

Typed Mode

Alternative to drawing — users type their name and it renders in a cursive font on the canvas.

<Form.Field.Signature name="signature" allowTyped={true} typedFont="'Dancing Script', cursive" />

Toggle between Draw and Type modes via the segmented control at the top.

Draw Only

Disable typed mode for cases where handwritten signatures are required:

<Form.Field.Signature name="signature" allowTyped={false} />

Custom Styling

<Form.Field.Signature
  name="signature"
  width={500}
  height={200}
  strokeColor="#1a365d"
  strokeWidth={3}
  backgroundColor="#f7fafc"
/>

Props

PropTypeDefaultDescription
widthnumber400Canvas width in pixels
heightnumber150Canvas height in pixels
strokeColorstring'black'Pen/stroke color
strokeWidthnumber2Pen/stroke width
backgroundColorstring'white'Canvas background
clearLabelstring'Clear'Clear button text
placeholderstring'Sign here'Placeholder over empty canvas
allowTypedbooleantrueShow typed signature mode
typedFontstringcursiveFont for typed mode

Accessibility

  • Canvas has role="img" and aria-label="Signature pad"
  • Tab focus shows visible outline
  • Typed mode serves as keyboard-accessible alternative
  • Clear button is keyboard-accessible

Touch Support

Full touch support for mobile devices:

  • touchAction: none prevents scrolling while drawing
  • Touch events (touchstart, touchmove, touchend) handled alongside mouse events

Validation

Use Zod z.string().min(1) to require a signature:

const Schema = z.object({
  signature: z.string().min(1, 'Signature is required'),
})

The field value is empty string '' when canvas is blank, and a data URI when drawn/typed.


Live Example

Try the interactive example on forms-example.letar.best.

On this page