Ask Anvil

Answers to questions about automating PDFs, e-signatures, Webforms, and other paperwork problems.
Webforms
Categories

How do I require a drawn signature before a web form can be submitted?

Give the form a second, invisible input that holds the signature, and mark that input invalid with setCustomValidity until the signer has actually drawn something. The browser then blocks submission and shows your message, with no extra validation library.

<form id="agreement">
  <canvas id="pad" width="400" height="120" style="touch-action: none"></canvas>
  <input id="sig" name="signature" aria-label="Signature" tabindex="-1"
         style="position: absolute; opacity: 0; width: 1px; height: 1px">
  <button type="button" id="clear">Clear</button>
  <button type="submit">Submit</button>
</form>
const form = document.getElementById('agreement')
const pad = document.getElementById('pad')
const sig = document.getElementById('sig')
const ctx = pad.getContext('2d')
let drawing = false
let moved = false

function setSigned(signed) {
  sig.value = signed ? pad.toDataURL('image/png') : ''
  sig.setCustomValidity(signed ? '' : 'Please sign in the box before submitting.')
}
setSigned(false)

function point(e) {
  const r = pad.getBoundingClientRect()
  return [e.clientX - r.left, e.clientY - r.top]
}

pad.addEventListener('pointerdown', (e) => {
  drawing = true
  moved = false
  pad.setPointerCapture(e.pointerId)
  ctx.beginPath()
  ctx.moveTo(...point(e))
})

pad.addEventListener('pointermove', (e) => {
  if (!drawing) return
  moved = true
  ctx.lineTo(...point(e))
  ctx.stroke()
})

pad.addEventListener('pointerup', () => {
  drawing = false
  if (moved) setSigned(true)
})

document.getElementById('clear').addEventListener('click', () => {
  ctx.clearRect(0, 0, pad.width, pad.height)
  setSigned(false)
})

Why this works

A form will not submit while any of its controls is invalid, and a control with a non-empty custom validity message counts as invalid. The canvas itself is not a form control, so it can never block anything. The text input is the bridge: it carries the data URL of the drawing when the signer has signed, and an error message when they have not.

Two things that will bite you

First, do not use type="hidden" for that input. Hidden inputs do not participate in constraint validation, so the form would submit unsigned. Make the input invisible with CSS instead (opacity and a 1px size here) and it stays a normal text input that the form validates.

Second, count a stroke, not a tap. The snippet only marks the form as signed after a pointermove, so a stray click does not pass as a signature. Set touch-action: none on the canvas as well. By default a touch drag is treated as a page scroll, and the browser cancels your pointer events partway through the stroke. That setting also disables pinch zoom on the canvas, which is fine for a small signing box but not for a whole page.

The check is a convenience for the signer, not a safeguard. Anyone can submit the form without running your script, so reject an empty signature value on the server too.

Back to All Questions

The fastest way to build software for documents

Anvil Document SDK is a comprehensive toolbox for product teams launching document flows where PDF filling, signing, and complex conditional scenarios are necessary.
Explore Anvil
Anvil Webforms
Marketing Mode