# SignaturePad

> Captures a hand-drawn signature with pointer, touch or stylus, with undo, clear and form submission.

Category: Forms · Status: stable · Package: @bindshq/stance-react@0.1.1 · Docs: https://stanceui.com/components/signature-pad

Use SignaturePad where a person signs a document on screen. Strokes are stored as points, so the drawing survives resizes and follows the theme. Drawing is not possible for everyone: always offer a typed or uploaded signature beside it, as the first example shows.

## Import

```tsx
import { SignaturePad } from '@bindshq/stance-react';
```

Styles are imported once per app: `@bindshq/stance-tokens/css` and `@bindshq/stance-react/styles.css`.

## Examples

### Draw, type or upload

```tsx
import * as React from 'react';
import { FileUpload, FormField, Input, SignaturePad, Tabs, TabsContent, TabsList, TabsTrigger } from '@bindshq/stance-react';

export function WithAlternatives() {
  const [typed, setTyped] = React.useState('');
  return (
    <div style={{ width: '100%', maxWidth: 520 }}>
      <Tabs defaultValue="draw">
        <TabsList aria-label="How to sign">
          <TabsTrigger value="draw">Draw</TabsTrigger>
          <TabsTrigger value="type">Type</TabsTrigger>
          <TabsTrigger value="upload">Upload</TabsTrigger>
        </TabsList>
        <TabsContent value="draw">
          <div style={{ paddingTop: 16 }}>
            <SignaturePad label="Your signature" name="signature" />
          </div>
        </TabsContent>
        <TabsContent value="type">
          <div style={{ paddingTop: 16 }}>
            <FormField
              label="Type your full name"
              description="Your typed name is used as your signature."
            >
              <Input value={typed} onChange={(e) => setTyped(e.target.value)} autoComplete="name" />
            </FormField>
          </div>
        </TabsContent>
        <TabsContent value="upload">
          <div style={{ paddingTop: 16 }}>
            <FileUpload
              accept="image/png,image/jpeg,image/svg+xml"
              maxSize={2 * 1024 * 1024}
              title="Upload an image of your signature"
              hint="PNG, JPG or SVG up to 2 MB"
            />
          </div>
        </TabsContent>
      </Tabs>
    </div>
  );
}
```

### Drawing pad

Undo removes the last stroke; Clear removes everything.

```tsx
import { SignaturePad } from '@bindshq/stance-react';

export function Basic() {
  return (
    <div style={{ width: '100%', maxWidth: 520 }}>
      <SignaturePad label="Your signature" />
    </div>
  );
}
```

### In a form

With a name, the signature is submitted as an SVG string (or a PNG data URL with format="png").

```tsx
import * as React from 'react';
import { Button, FormField, SignaturePad } from '@bindshq/stance-react';

export function InAForm() {
  const [submitted, setSubmitted] = React.useState<number | null>(null);
  return (
    <form
      style={{ width: '100%', maxWidth: 520, display: 'grid', gap: 16 }}
      onSubmit={(e) => {
        e.preventDefault();
        setSubmitted(String(new FormData(e.currentTarget).get('signature') ?? '').length);
      }}
    >
      <FormField
        label="Signature"
        description="Sign as your name appears on the agreement."
        required
      >
        <SignaturePad name="signature" />
      </FormField>
      <div>
        <Button type="submit">Sign agreement</Button>
      </div>
      <p role="status" style={{ margin: 0 }}>
        {submitted === null
          ? ''
          : submitted === 0
            ? 'Nothing was submitted: the pad is empty.'
            : `Submitted an SVG of ${submitted} characters.`}
      </p>
    </form>
  );
}
```

### Exporting

Use the ref to export SVG or PNG, or to clear the pad from your own buttons.

```tsx
import * as React from 'react';
import { Button, SignaturePad } from '@bindshq/stance-react';
import type { SignaturePadHandle, SignatureStroke } from '@bindshq/stance-react';

export function ImperativeHandle() {
  const pad = React.useRef<SignaturePadHandle>(null);
  const [strokes, setStrokes] = React.useState<SignatureStroke[]>([]);
  const [output, setOutput] = React.useState('');
  return (
    <div style={{ width: '100%', maxWidth: 520, display: 'grid', gap: 12 }}>
      <SignaturePad
        ref={pad}
        value={strokes}
        onChange={setStrokes}
        showActions={false}
        height={140}
      />
      <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
        <Button
          size="sm"
          variant="outline"
          onClick={() => setOutput(`${pad.current?.toSVG().length ?? 0} characters of SVG`)}
        >
          Export SVG
        </Button>
        <Button size="sm" variant="ghost" onClick={() => pad.current?.clear()}>
          Clear
        </Button>
      </div>
      <p role="status" style={{ margin: 0 }}>
        {strokes.length} stroke{strokes.length === 1 ? '' : 's'}
        {output ? ` · ${output}` : ''}
      </p>
    </div>
  );
}
```

### Read-only

Show a captured signature without allowing changes.

```tsx
import { SignaturePad } from '@bindshq/stance-react';
import type { SignatureStroke } from '@bindshq/stance-react';

const sample: SignatureStroke[] = [
  [
    { x: 40, y: 90 },
    { x: 60, y: 50 },
    { x: 85, y: 95 },
    { x: 105, y: 55 },
    { x: 130, y: 92 },
    { x: 160, y: 70 },
    { x: 200, y: 88 },
    { x: 250, y: 74 },
  ],
];

export function ReadOnly() {
  return (
    <div style={{ width: '100%', maxWidth: 520 }}>
      <SignaturePad label="Signature of Priya Nair" defaultValue={sample} readOnly height={140} />
    </div>
  );
}
```

## API reference

| Component | Prop | Type | Default | Description |
| --- | --- | --- | --- | --- |
| SignaturePad | `value` | `SignatureStroke[]` | None | Controlled strokes. Each stroke is an array of { x, y } points in CSS pixels. |
| SignaturePad | `defaultValue` | `SignatureStroke[]` | `[]` | Initial strokes when uncontrolled. |
| SignaturePad | `onChange` | `(strokes: SignatureStroke[]) => void` | None | Called when a stroke is finished, undone or cleared. |
| SignaturePad | `label` | `string` | `'Signature'` | Accessible name of the drawing area. |
| SignaturePad | `height` | `number` | `180` | Height of the drawing area in px. The width fills the container. |
| SignaturePad | `penWidth` | `number` | `2.5` | Stroke width in px. |
| SignaturePad | `name` | `string` | None | Adds a hidden input so the signature is submitted with a form. |
| SignaturePad | `format` | `'svg' \| 'png'` | `'svg'` | What the hidden input carries: an SVG string or a PNG data URL. |
| SignaturePad | `showActions` | `boolean` | `true` | Show the Undo and Clear buttons. |
| SignaturePad | `disabled` | `boolean` | `false` | Prevents drawing and removes the pad from the tab order. |
| SignaturePad | `readOnly` | `boolean` | `false` | Shows the signature without allowing changes. |
| SignaturePad | `invalid` | `boolean` | None | Marks the pad invalid. Set automatically inside an erroring FormField. |
| SignaturePad | `labels` | `Partial<SignaturePadLabels>` | None | Translations for buttons, the hint, announcements and the description. |
| SignaturePad | `ref` | `Ref<SignaturePadHandle>` | None | clear(), undo(), isEmpty(), toDataURL(type?) and toSVG(). |

## Accessibility

- Drawing needs fine pointer control, so it must never be the only way to sign. Offer typing a name or uploading an image alongside it (WCAG 2.5.1, 2.5.7).
- The pad is exposed as an image whose name states whether a signature is present, for example “Signature: no signature yet”.
- A description tells keyboard and screen-reader users to use the typed or uploaded alternative.
- Capturing, undoing and clearing are announced through a polite live region.
- Undo and Clear are real buttons; Backspace or Delete on the focused pad also removes the last stroke.
- Ink uses the theme’s text colour, so it keeps full contrast in light, dark and high contrast.

## Keyboard

| Keys | Action |
| --- | --- |
| `Tab` | Moves focus to the pad, then to Undo and Clear. |
| `Backspace` `Delete` | Removes the last stroke while the pad is focused. |

## Guidelines

**Do**

- Offer Draw, Type and Upload together, and let the person choose.
- State what signing means next to the action: “By signing, you agree to…”.

**Don’t**

- Make a drawn signature the only way to complete a document.
- Treat the drawing as proof of identity; that belongs to your product’s signing process.

## Related

- https://stanceui.com/components/file-upload
- https://stanceui.com/components/tabs
- https://stanceui.com/components/form-field
