# vue-signature-pad: a signature pad component for Vue

> Drawing a signature on a canvas in the browser is a solved problem, and the interesting question is what a framework wrapper should add to it. This one's answer is: very little. It exposes the underlying library's options object directly, it adds a couple of props that the framework makes easy, and it takes care of the awkward part, which is that a canvas drawn on a high density screen is blurry unless somebody scales it.

**neighborhood999/vue-signature-pad** — 🖋 Vue Signature Pad Component

- Repository: https://github.com/neighborhood999/vue-signature-pad
- Stars: 561 · Forks: 76
- Language: JavaScript
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/neighborhood999-vue-signature-pad

## What it wraps, and the version split

This is a Vue component that wraps an existing signature pad library, the canvas based one that draws with a pointer and produces a PNG of the signature. The wrapper's job is to make that library behave like a Vue component: take props, expose methods, and work with the framework's own idioms for registration and for template references.

```sh
$ yarn add vue-signature-pad
```

The most important line in the readme is a version note rather than a feature. If you are still using the second generation of Vue, install the version whose number ends in five from the second major line. For the third generation, install the latest published version.

That is a significant piece of housekeeping, and it is the kind that exists because the component system itself changed between the two major versions. The second generation had a global plugin model, where a component was installed once on the application object and then available everywhere by name. The third replaced it with per-application registration, and the component's own registration has to change with it.

So the package is two components wearing one name, and the readme tells you which one you want rather than trying to ship a version that works with both. That is a defensible decision, and the alternatives are worse: a component that works with both has to detect the framework version at run time and register itself twice.

The badge row says what else matters. There is the build status, the published version on the package registry, a badge saying the code is styled with a particular formatter, and one listing the module formats the package ships in: the common module format, the module format, and the universal one. That last badge is the one to look at for a component library, because it tells you whether you can use it in a modern bundler without a compatibility shim.

And there are two live demos, one for each generation of the framework, hosted as editable sandboxes rather than as videos. That is the right way to demo a component, because the reader can change the code and see the result, and it also means the demos cannot rot without somebody noticing.

The library is MIT licensed, the last push is recent, and the tagged releases are in the third major line with the newest from a couple of years ago, which is the release cadence of a component that has settled.

## Two integration styles, and how you reach the methods

The readme gives three usage examples and they differ in a way that is worth understanding, because the choice is not stylistic.

The first is global installation for the older framework, where the component is imported and registered on the application object in one line. The second is per-application registration for the newer framework, where the component is imported by name and registered on the created application, also in one line, and then used in a template by that name. The third is importing it inside a single component and listing it in that component's own registry, which is the approach that does not make the component global at all.

So the choice is between the three is: available everywhere, available in one place, or global but registered on the application rather than through a plugin. The third example exists because global registration through a plugin is the older pattern and some projects still use it in the newer framework, and because a component that is only used in one place should not be global.

The method access is the same in all three examples, and it is the part that tells you the component is a thin wrapper. The template gives the component a reference, and the methods in the parent reach into that reference to call the underlying library's methods.

The save method is the interesting one, because it returns two things: whether the canvas is empty, and the data. The data is the signature as an image, and the empty flag is what lets a form distinguish a signature the user drew from a signature they skipped.

That two-value return is a small design decision with real consequences for the consumer. A form that validates whether a signature is present needs the flag, because an empty canvas still produces an image, and an image of nothing is not the same as no signature. A single return value would force every consumer to inspect the image to work that out.

And the undo method is the other half of the minimum viable signature widget. Undo is a requirement rather than a nicety, because people make mistakes, and a signature field with no undo is a signature field that produces a lot of retyped forms.

The templates in the examples also show the width and height passed as attributes, and the two buttons that call the two methods. That is the entire component surface: draw, save, undo.

## The props, and the two that are not obvious

The props table is the real documentation of the component, and most of it is unsurprising. The width and the height are strings that set the size of the container, and both default to filling the available space, which is the right default for a component that has to work in a form of unknown layout.

The first prop that is not obvious is the images one. It takes an array, and the description is to merge the signature with the provided images. The examples show two forms: an array of paths, and an array of objects each with a source and a position. So you can stamp a pre-existing image under the signature, and you can say where it goes.

That is a feature with a specific use: a form that asks a person to sign over a picture of a contract clause, or an application that wants a signature on a document background. The underlying library does the work, and the wrapper exposes the array as a prop with a shape that covers both the simple case and the positioned case.

The second is the scale-to-device-pixel-ratio prop, and it is the one that saves the component from looking broken on a modern phone. A canvas has a fixed number of pixels, and a phone screen has several physical pixels per logical pixel, so a canvas drawn at the logical size looks soft on a high density display. The fix is to scale the canvas up to match the device pixel ratio, and the prop does that, and it is on by default.

The reason this is a prop rather than unconditional behaviour is that scaling costs memory and rendering time, and in a case where the signature is small, or the device is an old one with a low ratio, the extra work buys nothing. Defaulting it on is right, because the majority of devices today are high density, and exposing it lets the unusual case opt out.

The options prop is the escape hatch and it is where the design philosophy shows. Its default value is the underlying library's own default settings, and the readme links to a specific line range in a source file as the reference for what those defaults are. So the component does not define its own notion of what a signature pad should be; it forwards the library's, and the library is the documentation.

And the custom style prop is a plain object applied to the container, which is the minimum needed for a component to be styled by the application around it rather than only by the framework.

## Events, and the decision not to wrap the API

The event story is one sentence in the readme and it is a good one.

The library uses the default settings of the underlying signature pad as the options, and you are free to customise them to whatever you want. And in a particular release, the two event callbacks for the start and the end of a stroke were added.

So the two events are not a separate Vue event system. They are passed in through the options object, exactly as they would be if you were using the underlying library directly, with the callbacks written as functions in the parent component that log when the drawing starts and when it ends.

That is a small choice, and it is the component's design philosophy in miniature. The wrapper does not define a parallel event vocabulary of its own; it takes the library's options object and passes it through, and the library's callbacks become the component's callbacks.

The alternative would be to expose the events as first class props, with a documented set of names, and to translate the library's options into them. That is nicer to type and to discover, and it is also a promise: once you have published the event names, you cannot change the underlying library's API without breaking your consumers. The pass-through approach means the wrapper's surface is the library's surface, and the library can change without the wrapper having to pretend it did not.

The cost of that decision is discoverability. A reader of the wrapper's documentation has to go and read the library's documentation to find out what it can do, and the readme handles this by linking to the reference in the source file where the defaults are defined. That link is the whole integration story for anything beyond the two methods and the handful of props, and it points into a file rather than into prose, which is a good sign: the defaults are code, and the code is the specification.

For a component whose entire value is that it does one thing well, that is the right balance. There is no configuration surface to learn, no second API to remember, and no layer between you and the library when something does not work.

## The build, and four module formats

The build is a bundler invocation with four scripts, and the four are the module formats the package ships in, which the badge row advertises.

The main entry point is a server rendering build in the common module format. The module field points at the module format build, and the browser field points at the same file, which is the modern convention for a package that is bundled rather than loaded from a tag. The unpkg field points at a minified universal build for people who want to drop it into a page from a content delivery network.

The published file list is worth noting: the built distribution and, in addition, the single file components as source. Shipping the source of the component is an unusual choice, and it is a generous one, because a consumer who wants to modify the component can find it in the package rather than having to fork the repository. It also means the file list has to be maintained by hand, since it is a glob rather than a directory listing, and that is the kind of thing that quietly stops matching when a directory is renamed.

The build script itself cleans the output directory first and then runs the bundler, and the clean is a separate script that removes the output directory with a cross platform tool rather than with a shell command, which is the detail that lets the same script work on a developer's Windows machine.

Two scripts are about publishing discipline. One runs the tests and the linter before a publish, so a broken build cannot reach the registry. The other installs the version control hooks, which is what makes the staged-file linting work.

And the staged-file configuration runs the linter with the fix flag on the JavaScript and the single file components that are about to be committed, so that a commit is linted before it is made rather than by a continuous integration job afterwards. The continuous integration badge in the readme is from a service that has since been folded into another, so the badge is a historical artifact, but the configuration behind it is not.

There is also a development server script and a development directory with its own entry point, so that the component can be exercised in a page without publishing it, and a separate build configuration file for the individual formats.

## Testing a canvas component, and the size budget

The test configuration is the most instructive part of the manifest, because it solves a problem that is specific to this kind of component.

The test runner is the usual JavaScript one, and the configuration is detailed. The file extensions it will resolve include both JavaScript and the single file component format. The test files are matched in a tests directory inside each source directory. The transforms are the runner's JavaScript transform and a separate one for the component format, because the two file types have to be compiled differently before the runner can execute them. There is a path alias mapping so that the tests can import through the same alias the source uses. Coverage collection is on, and the runner is verbose.

And then the entry that matters: a setup file that installs a mock for the canvas. Because the test environment has no canvas implementation, and this component's entire job is drawing on one, every test that touches the drawing surface would fail without it. The mock is a small library that stands in for the browser's canvas, and adding it as a setup file is what lets the tests run at all.

That is a good example of a testing decision that comes straight out of what the code does rather than out of a convention. A component library whose tests all pass and whose component cannot draw is worse than no tests, and the mock is the specific concession that makes these tests mean something.

The other thing in the manifest that deserves attention is the size budget. There is a script that runs three sub-scripts, one per module format, and each of them reports the gzipped size of the built file. And at the top level of the repository there is a file recording the size snapshots.

So the project measures its own bundle size on every build and keeps a record of it. For a component whose selling point is that it does one small thing, that is a reasonable thing to keep an eye on, and the compression is the right measure because it is the compressed size that a browser downloads.

The rest of the repository is conventional and thorough: a lint configuration and a file of paths to ignore, an editor configuration, a browsers list that says which browsers the output is compiled for, a dependency update configuration, a continuous integration file, a hook directory, and a lock file for the package manager. The dependency update configuration is the one that keeps a component library alive, because a component that is not rebuilt against current dependencies stops being installable within a year.

## Conclusion

vue-signature-pad fits a Vue application that needs a signature on a form and would rather not wire up a canvas by hand, and it fits one that has to work on both a phone and a desktop. It does not fit a project that needs the raw drawing API of the underlying library, because the wrapper deliberately exposes only what it exposes. Verify first which major version of Vue your application is on, since the second major is for the older one and the latest is for the newer, that the events you need are available in the version you install, since the pair of begin and end callbacks arrived in a later release, and that the exported image format is what your server expects, since the wrapper hands you the library's own output rather than re-encoding it.

## FAQ

### What does the vue-signature-pad package do?

It provides a Vue component that wraps the signature pad canvas library, so that a form can accept a drawn signature. It exposes methods to save the signature as an image, to tell whether the canvas is empty, and to undo the last stroke, and it works with both the second and the third generation of Vue.

### Which version of vue-signature-pad should I install?

If you are on the second generation of Vue, install the version whose number ends in five from the second major line. For the third generation, install the latest published version. The component's registration differs between the two, and the package ships one component per major line rather than trying to detect the framework version at run time.

### What props does vue-signature-pad accept?

Width and height as strings that both default to filling the available space, an options object whose default is the underlying library's own default settings, an images array for merging existing images under the signature, a custom style object for the container, and a flag that scales the canvas up to the device pixel ratio, which is on by default and is what keeps the signature sharp on a high density screen.

### How do I handle the start and end of a stroke in vue-signature-pad?

Through the options object, where the two callbacks were added in a later release of the package. The component does not define its own event vocabulary; it passes the underlying library's options through, so the callbacks are written in your parent component and referenced from the options prop.

### How does vue-signature-pad test a canvas component?

The test configuration installs a mock for the canvas as a setup file, because the test environment has no canvas implementation and the component's whole job is drawing on one. The tests are written in the standard JavaScript runner with a separate transform for the single file component format, a path alias so the tests import the same way the source does, and coverage collection enabled.

## Sources

- [Issues](https://github.com/neighborhood999/vue-signature-pad/issues)
- [License: MIT](https://github.com/neighborhood999/vue-signature-pad/blob/master/LICENSE)
- [neighborhood999/vue-signature-pad on GitHub](https://github.com/neighborhood999/vue-signature-pad)
- [README](https://github.com/neighborhood999/vue-signature-pad/blob/master/README.md)
- [Releases](https://github.com/neighborhood999/vue-signature-pad/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/neighborhood999-vue-signature-pad
