html2canvas alternatives. The short version.
the direct answer
Use modern-screenshot if you want the smallest current option, html-to-image if you are migrating off dom-to-image, and satori if the image has to exist before a browser does. All three are drop-in enough that the choice is reversible, and none of them is html2canvas.
why the question is asked at all
The latest release of html2canvas is 1.4.1, published 22 January 2022, read from registry.npmjs.org on 26 August 2026, which puts it four and a half years without a release. It is also the largest of the browser-side options by an order of magnitude: 3.38 MB unpacked across 391 files, against 186 KB across 12 for modern-screenshot.
That size difference is not incidental, and it is the one thing worth understanding before picking. html2canvas reimplements layout, it walks your DOM and repaints it onto a canvas itself, which is why it needs css-line-break and text-segmentation and why it can disagree with what you saw on screen. Every other browser-side package in the table borrows the engine already in the room: serialise the node, wrap it in an SVG <foreignObject>, let the browser rasterise it. That technique is written out in full here.
what every one of them shares
Switching packages does not switch you out of the canvas security rule. Per MDN, as soon as any of them draws data loaded from another origin without CORS approval, the canvas becomes tainted, and toBlob(), toDataURL() and captureStream() throw a SecurityError. If your export throws today, a different library will throw the same thing tomorrow. Inline the asset, serve it same-origin, or send CORS headers.
The blank-export failure is the same story. The SVG is loaded as a data URI, so it is a sandbox with no origin: it cannot fetch a remote image, a webfont or a stylesheet. Whatever the picture needs has to be inside the string before it is serialised, by any of these packages, in any order.
So the honest framing is that these are not six different capabilities. Five of them are the same four browser APIs with different amounts of ceremony around them, and the sixth runs somewhere else entirely.
Six packages. Read from npm today.
version, deps, licence · published, unpacked size, files
modern-screenshot | 4.7.0 · 0 deps · MIT | 16 Apr 2026 · 186 KB · 12 files. The smallest general-purpose option, and a direct descendant of dom-to-image's approach. |
@zumer/snapdom | 3.1.1 · 0 deps · MIT | 24 Sep 2026 · 566 KB · 6 files. The most recently published of the six. Its version 3 registry description no longer names html2canvas at all: it now reads “Browser capture engine for web interfaces. Reusable images and canvas, with plugins for HTML, context, PDF and recordings.” |
html-to-image | 1.11.13 · 0 deps · MIT | 14 Feb 2025 · 315 KB · 82 files. The most widely used fork of dom-to-image, and the usual drop-in when dom-to-image stops being maintained enough. |
html2canvas | 1.4.1 · 2 deps · MIT | 22 Jan 2022 · 3.38 MB · 391 files. Pulls in css-line-break and text-segmentation. Reimplements layout rather than borrowing the browser's, which is why it is ten times the size of the others. |
dom-to-image | 2.6.0 · 0 deps · MIT | 4 Oct 2017 · size not reported. The original foreignObject implementation. Everything below it in this table is downstream of it. |
satori | 0.33.4 · 13 deps · MPL-2.0 | 24 Aug 2026 · 5.85 MB · 37 files. Not a browser rasteriser at all, it converts HTML and CSS to SVG on a server. The only one here that is not MIT. |
how to read this
Registry facts, not a review. Every cell is a field the npm registry returned on 26 August 2026, latest version, publish date of that version, declared licence, the length of the dependencies object, and dist.unpackedSize. No download counts, no stars, no opinion dressed up as a metric.
Five of the six are MIT with zero runtime dependencies. The two exceptions are the two to think about: html2canvas declares two dependencies, and satori declares thirteen and is MPL-2.0, which is a copyleft licence and a decision your legal position may already have made for you.
Publish dates are the field people misread. An old date means the package has not changed, not that it has stopped working, dom-to-image last shipped in 2017 and still does exactly what it did then. It means nobody is going to fix the next browser change for you.
These numbers go stale. That is why each carries the date it was read, and why a job re-reads them rather than a person promising to. scripts/verify-compare.mjs in this repository re-fetches every packument this page prints a figure from and fails when the version or the file count no longer matches. It ran on 25 September 2026 and moved @zumer/snapdom from 3.1.0 to 3.1.1, its unpacked size from 563,109 to 565,573 bytes.
Every one of these packages also sits on the register of every DOM to image option, which puts all seven columns against the same six criteria and ranks none of them. This page answers which one to pick; that one answers what they are.
dom-to-image vs html2canvas.
the answer first
Pick neither, and take html-to-image or modern-screenshot instead. They are the maintained descendants of dom-to-image, they use the same technique, and they have the same API shape, so the comparison people actually want is dom-to-image's approach against html2canvas's approach, and on that question dom-to-image's approach won and is what everything current is built on.
the difference that matters
dom-to-image hands your markup to the browser inside an SVG foreignObject and lets the real layout engine draw it. html2canvas parses your CSS and paints the result itself. The first is fast, small, and matches what the user saw, because it IS what the user saw. The second is slower, larger, and can drift from the page, but it does not depend on foreignObject support, and it can reach some things the sandbox cannot.
In package terms, on 26 August 2026: dom-to-image 2.6.0 with 0 dependencies, last published October 2017, no size reported; html2canvas 1.4.1 with 2 dependencies, last published January 2022, 3.38 MB across 391 files.
where each one actually breaks
dom-to-image and its descendants fail quietly. A remote image, a webfont or an external stylesheet does not load inside the data-URI sandbox, so the export comes out blank or half-drawn with nothing thrown. The fix is always the same: inline it before serialising.
html2canvas fails visibly instead. Because it is interpreting CSS rather than rendering it, an unsupported property comes out wrong on the canvas while the page looks fine, a gradient flattened, a filter dropped, a transform ignored. You get an image, just not your image.
Both throw the same SecurityError on a tainted canvas, and neither can do anything about it. That one is the browser's rule, not the library's.
The failure modes are enumerated with fixes on the DOM-to-PNG write-up, and the server-side fork, where satori sits, and why a crawler forces it, is compared separately.
Which one, for which job.
the requirement · the package
You already ship html2canvas and it works | Leave it. Nothing above says it is broken, it is unmaintained since January 2022 and large, which are reasons not to ADD it, not reasons to rip it out of something that renders correctly today. |
You want the smallest general-purpose option | modern-screenshot. 186 KB unpacked across 12 files, no runtime dependencies, published April 2026. It takes the dom-to-image approach and keeps it current. |
You are replacing dom-to-image in an existing codebase | html-to-image. It is the fork with the closest API to the original, so the migration is mostly an import path, and it has shipped this decade. |
You want the most actively published option | @zumer/snapdom, published 9 September 2026, one day before these figures were read. Newest release is not the same as most proven; treat it as a reason to look, not a reason to switch. |
You need an Open Graph or share card a crawler will see | None of the five. A crawler fetching your OG image never runs your JavaScript, so the picture has to exist server-side. That is satori's job, and it is MPL-2.0 rather than MIT, which is a licence question before it is a technical one. |
You want the receipt look, and the export with it | Tearline, and only then. It is a custom element, not a rasteriser you can point at an arbitrary div. If your requirement is “a PNG of this element” rather than “a receipt of this content”, one of the five above is the right answer. |
where Tearline is the wrong answer
Most of the time. Tearline is a custom element that renders whatever you wrap in it as a thermal receipt, paper texture, torn edge, barcode, and exports that. It is 27 KB across 4 files with no dependencies, and it is not a library you can point at an arbitrary div. If the requirement is “turn this existing element into a PNG”, five of the six packages above do that and this one does not.
where it is the right one
When the receipt itself is the point: a wrapped-up listening history, an order summary, a share card built out of rules and monospace. Then the look and the export arrive together instead of being a rasteriser bolted onto a div you styled yourself.
The attribute and method reference is in the documentation, the component is served unminified at /tearline.js, and the CSS behind the paper look is a separate write-up.
Every number here. Fetched, not remembered.
checked 26 August 2026
registry.npmjs.org | Latest version, publish date, licence, runtime dependency count, unpacked size and file count for html2canvas, dom-to-image, html-to-image, modern-screenshot, @zumer/snapdom, satori and @compoundlabs/tearline. @zumer/snapdom and @compoundlabs/tearline were re-fetched 19 September 2026 and both had moved, snapdom from 2.24.17 to 3.0.0 and Tearline from 0.1.0 to 0.2.0; html2canvas, dom-to-image, html-to-image, modern-screenshot and satori were re-fetched the same day and returned the same figures. |
developer.mozilla.org, CORS enabled image | The tainted-canvas rule: drawing cross-origin data without CORS approval taints the canvas, and toBlob(), toDataURL() and captureStream() then throw a SecurityError. Applies to every package in the table that runs in a browser. Fetched 26 August 2026. |
tearline.thecompound.tech/tearline.js | The Tearline component itself, served unminified at 22,766 bytes. 27 KB unpacked across 4 files on npm as @compoundlabs/tearline 0.2.0. |
why this is here
A comparison page is only worth reading if you can check it. Versions and publish dates go stale within weeks and a page quoting them from memory is wrong long before it looks wrong, so every figure above carries the date it was read and the source it came from.
If a number here has drifted, the registry is the authority, not this page.
WHEN THE ELEMENT CHANGES
Tearline is one custom element with six attributes, three methods and no dependencies, served as an MIT package. Attributes get added and the export stages change with them. Leave an address and Tearline writes when a release changes the API.