# Form field with validation

> Native form field that submits an SVG signature, with a required check and reset.

A shadcn/ui block from the [@reui](https://reui.io) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/reui/c-signature-pad-3).

- Page, with a live preview: https://shadcn.exchange/r/reui/c-signature-pad-3
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/reui/c-signature-pad-3-light-e3229964e2158bb3.png
- Registry item: https://reui.io/r/new-york-v4/c-signature-pad-3.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://reui.io/r/new-york-v4/c-signature-pad-3.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `button`, `field`, `input`, `@reui/signature-pad`

## Files

### `c-signature-pad-3.tsx`

```tsx
"use client"

import { useId, useState } from "react"
import {
  SignaturePad,
  SignaturePadArea,
  SignaturePadGuide,
} from "@/components/reui/signature-pad"

import { Button } from "@/components/ui/button"
import {
  Field,
  FieldDescription,
  FieldError,
  FieldGroup,
  FieldLabel,
} from "@/components/ui/field"
import { Input } from "@/components/ui/input"

export function Pattern() {
  const id = useId()
  const [invalid, setInvalid] = useState(false)
  const [submitted, setSubmitted] = useState<{
    name: string
    size: number
  } | null>(null)

  return (
    <form
      noValidate
      className="mx-auto w-full max-w-md"
      onSubmit={(event) => {
        event.preventDefault()
        const data = new FormData(event.currentTarget)
        /* The pad writes an SVG data URL into the form under its `name`. */
        const signature = String(data.get("signature") ?? "")
        setInvalid(!signature)
        setSubmitted(
          signature
            ? {
                name: String(data.get("name") ?? "").trim() || "Unnamed signer",
                size: new Blob([signature]).size,
              }
            : null
        )
      }}
      onReset={() => {
        setInvalid(false)
        setSubmitted(null)
      }}
    >
      <FieldGroup>
        <Field>
          <FieldLabel htmlFor={`${id}-name`}>Full name</FieldLabel>
          <Input
            id={`${id}-name`}
            name="name"
            autoComplete="name"
            placeholder="Maya Chen"
          />
        </Field>
        <Field data-invalid={invalid || undefined}>
          <FieldLabel id={`${id}-label`}>Signature</FieldLabel>
          <SignaturePad
            name="signature"
            format="svg"
            required
            onStrokeStart={() => setInvalid(false)}
          >
            <SignaturePadArea
              aria-labelledby={`${id}-label`}
              aria-describedby={`${id}-hint`}
              aria-invalid={invalid || undefined}
            >
              <SignaturePadGuide />
            </SignaturePadArea>
          </SignaturePad>
          {invalid ? (
            <FieldError id={`${id}-hint`}>Sign above to continue.</FieldError>
          ) : (
            <FieldDescription id={`${id}-hint`}>
              Use a mouse, a finger or a stylus.
            </FieldDescription>
          )}
        </Field>
        <div className="flex items-center gap-2">
          <Button type="submit">Submit</Button>
          <Button type="reset" variant="outline">
            Reset
          </Button>
          <p
            role="status"
            className="text-muted-foreground ms-auto min-w-0 truncate text-sm"
          >
            {submitted &&
              `Signed by ${submitted.name}, ${(submitted.size / 1024).toFixed(1)} KB SVG`}
          </p>
        </div>
      </FieldGroup>
    </form>
  )
}
```
