Signature Pad — Free HTML CSS JS Canvas Snippet

Signature Pad · Forms · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Quadratic-curve stroke smoothing — midpoint-based quadraticCurveTo() rendering turns raw pointer samples into a continuous, natural-looking line instead of a faceted polyline
Strokes stored as data, not pixels — every pen stroke is an array of points, so a single redraw() function can rebuild the entire drawing from scratch after any change
One-line undo and clear — strokes.pop() plus redraw removes the last stroke; emptying the array and clearing the canvas resets everything, both with zero pixel manipulation
High-DPI canvas scaling — sizes the drawing buffer by window.devicePixelRatio so signatures stay sharp on Retina displays and modern phones instead of rendering soft
Switchable ink colours — three swatch buttons update a shared color variable so new strokes pick up whichever ink is active, with an animated active-state ring
Unified mouse + touch input — a single pos() helper normalizes both pointer families into canvas coordinates, with touch-action: none so signing on mobile does not scroll the page
One-click PNG export — canvas.toDataURL("image/png") plus a programmatic <a download> click saves the finished signature as an image with no server or library involved

About this UI Snippet

How this signature pad was built — smoothed canvas strokes and a stroke-based undo stack

Screenshot of the Signature Pad snippet rendered live

This snippet recreates the "sign here" widget you find in contract tools, delivery confirmations, and onboarding forms — draw with a mouse or finger and the line comes out smooth, not jagged, with undo, clear, multiple ink colours, and a one-click PNG export. It runs on a single <canvas> and about 90 lines of vanilla JavaScript, with no signature library required.

Smoothing strokes with quadratic curves

The naive way to draw a freehand line is to connect every raw pointer position with straight lineTo segments — which looks visibly faceted at normal drawing speeds. Instead, this snippet stores each stroke as an array of points and redraws it with ctx.quadraticCurveTo(), using the *midpoint* between each pair of consecutive points as the curve's end coordinate and the point itself as the control point. That midpoint trick is the classic technique behind smooth freehand drawing — it threads a continuous curve through a series of raw samples so corners round off naturally, a related idea to the particle-trajectory smoothing you'll find in our Floating Particles snippet — both threading continuous motion through a series of raw, jittery samples.

Strokes as data, not pixels — which makes undo trivial

Rather than drawing directly onto the canvas and leaving permanent pixels behind, every pen-down starts a new { color, points: [] } object pushed onto a strokes array, and a single redraw() function clears the canvas and replays every stroke from scratch on each pointer move. Because the drawing is *data-driven*, "Undo" is just strokes.pop() followed by a redraw — popping the most recent stroke off the array and re-rendering everything that's left. "Clear" is the same idea taken to its conclusion: empty the array, wipe the canvas. There's no pixel manipulation involved in either action, which keeps the code short and the behavior perfectly predictable.

High-DPI canvas and unified pointer handling

The canvas is sized using window.devicePixelRatio, scaling the drawing buffer up and calling ctx.scale(ratio, ratio) so strokes stay crisp on Retina and high-density mobile screens instead of looking soft and blurry. A small pos() helper normalizes mousedown/mousemove and touchstart/touchmove into the same {x, y} shape via getBoundingClientRect(), so the exact same start/move/end functions drive both desktop and touch input — paired with touch-action: none on the canvas so signing on a phone doesn't also scroll the page.

Exporting as a PNG

The "Download PNG" button is a one-liner: canvas.toDataURL('image/png') returns a base64-encoded image of the current canvas, which gets assigned to a temporary <a download> link and clicked programmatically — the same export pattern used by the QR Code Generator snippet. No server round-trip, no image-processing library — the browser does all the encoding.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You do not have to trace the stroke-smoothing math by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to walk through why quadraticCurveTo is called with the midpoint between consecutive points rather than the raw point itself, or why storing strokes as an array of point objects instead of drawing straight to the canvas is what makes undo a one-line operation. The same assistant can help optimize it, for instance checking whether redrawing every stroke from scratch on each pointermove becomes a bottleneck with very long signatures, or whether the devicePixelRatio scaling needs to account for window resizes. It is just as useful for extending the pad: ask it to add a redo stack alongside undo, support variable stroke width based on drawing speed, or export the signature as an SVG path instead of a PNG. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a canvas-based "signature pad" in plain HTML, CSS, and JavaScript using only the Canvas 2D API — no signature library, no dependencies.

Requirements:
- A canvas element scaled for high-DPI screens: multiply its width and height by window.devicePixelRatio and call ctx.scale(ratio, ratio) so strokes render crisply on Retina and high-density mobile displays.
- Store drawing as data, not pixels: every pointer-down must start a new stroke object holding a color and an array of points, pushed onto a shared strokes array. Every pointer-move must append the current position to that stroke's points array — never draw directly to the canvas from a single move event.
- A single redraw function must clear the entire canvas and replay every stroke in the strokes array from scratch, smoothing each one with ctx.quadraticCurveTo, using the midpoint between each pair of consecutive points as the curve's endpoint and the point itself as the control point, so the line looks continuously curved instead of a faceted polyline of straight segments.
- Implement Undo as popping the most recently added stroke off the strokes array and calling redraw — no separate undo history or canvas snapshots. Implement Clear as emptying the strokes array and clearing the canvas.
- Normalize mouse and touch input through one helper function that returns the same {x, y} shape for both mousedown/mousemove and touchstart/touchmove, using getBoundingClientRect for coordinate translation, and set touch-action: none on the canvas so signing on a touchscreen does not also scroll the page.
- Support switchable ink colors via clickable swatch buttons that update a shared color variable used only when a new stroke begins.
- Add a "Download PNG" action that calls canvas.toDataURL("image/png") and triggers a programmatic download via a temporary anchor element — no server round-trip.

Step by step

How to Use

  1. 1
    Set up a high-DPI canvasMultiply the canvas's width and height by window.devicePixelRatio and call ctx.scale(ratio, ratio) so strokes render crisply on Retina and high-density mobile displays instead of looking blurry.
  2. 2
    Record strokes as point arrays, not pixelsOn pointer-down, push a new { color, points: [pos(e)] } object onto a strokes array. On every pointer-move, append the new position to current.points — the canvas never gets drawn on directly during input.
  3. 3
    Smooth the line with quadraticCurveToIn redraw(), walk each stroke's points and call ctx.quadraticCurveTo(point, midpoint) between consecutive samples — using the midpoint as the curve endpoint rounds off the line instead of leaving visible facets.
  4. 4
    Make undo and clear pure array operationsImplement "Undo" as strokes.pop() followed by redraw(), and "Clear" as emptying the array and calling ctx.clearRect() — both actions stay perfectly in sync because the canvas always reflects exactly what is in strokes.
  5. 5
    Wire ink colour swatchesGive each swatch button a data-color attribute; on click, toggle an .active class and update a shared color variable that gets assigned to new strokes as they start.
  6. 6
    Export with toDataURLOn the download button's click handler, set a temporary <a> element's href to canvas.toDataURL("image/png") and its download attribute to a filename, then call .click() to trigger the browser's save dialog.

Real-world uses

Common Use Cases

Contract, agreement, and consent forms
Drop a signature pad into a rental agreement, NDA, waiver, or terms-acceptance flow — capture a real signature image instead of a typed name, and export it as a PNG to attach to the submitted record.
Delivery and service confirmations
Use the canvas as a "sign on receipt" widget for courier, repair, or field-service apps — undo lets the signer fix a mistake without starting completely over, and the colour swatches suit different ink-style preferences.
Onboarding and profile personalization
Let users draw a personal signature, monogram, or doodle as part of account setup — export it as a transparent-friendly PNG to use as an avatar flourish or email sign-off image.
Learning canvas freehand drawing
A compact, real-world example of the exact techniques — pointer normalization, stroke smoothing, and data-driven redraw — that underlie every drawing app, from simple sketch pads to full illustration tools.
A foundation for richer drawing tools
Extend the strokes data model with variable line widths, eraser strokes, or a redo stack — because every action is just an array operation followed by a redraw, new features compose cleanly on top.
A reference for canvas-to-image export
The toDataURL() + programmatic-download pattern here is the same one used by the QR Code Generator — copy it any time you need to let users save canvas content as an image file.

Got questions?

Frequently Asked Questions

Avoid connecting raw pointer samples with straight lineTo segments — at normal drawing speed that produces a visibly faceted line. Instead, store each stroke as an array of points and render it with ctx.quadraticCurveTo(), using the midpoint between each consecutive pair of points as the curve's endpoint and the point itself as the control point. That threads a continuous curve through the samples and rounds off corners naturally — exactly what this signature pad does in its redraw() function.

By treating each stroke as data rather than pixels. Every pen-down pushes a new { color, points: [] } object onto a strokes array, and the canvas is always rebuilt from that array via a redraw() call. "Undo" is therefore just strokes.pop() (remove the most recent stroke) followed by a redraw — no pixel snapshots, no memory-heavy history stack, just one array operation.

On Retina and most modern phone screens, one CSS pixel maps to two or three physical device pixels. If the canvas's internal resolution matches only its CSS size, strokes render at a lower resolution than the screen and look soft or blurry. Multiplying the canvas's width/height by window.devicePixelRatio and calling ctx.scale(ratio, ratio) renders at full device resolution while keeping coordinates in familiar CSS-pixel units — so signatures stay crisp.

Yes — a pos() helper reads e.touches?.[0] ?? e so the same coordinate math drives both mousemove and touchmove. The canvas also sets touch-action: none, which prevents the browser from interpreting a finger drag as a page-scroll gesture, so signing on a phone or tablet feels natural rather than fighting the scroll.

Call canvas.toDataURL("image/png"), which returns a base64-encoded PNG of the canvas's current contents. Assign that string to a temporary <a> element's href, set its download attribute to a filename like signature.png, and call .click() programmatically — the browser handles the save dialog with no server round-trip or image library required.

Yes — copy the HTML, CSS, and JS with the buttons on this page and use them anywhere, including commercial products, with no attribution required. It is built entirely on the native Canvas 2D API and vanilla JavaScript, so there are no licensing terms or third-party dependencies to track.