QR Code
Generate and scan QR codes in the browser.
npm install @devix-labs/qr-code
A QR encoder verified module for module against an independent implementation across 1440 version, level, mode and mask combinations. A logo option that punches out the modules underneath, raises the error correction to carry it, and refuses to promise a scan it cannot deliver — using a budget measured by covering codes until they stopped decoding, not the headline tolerance. SVG output as two paths whatever the version, PNG rounded up to whole module pixels, and camera scanning on the browser's own decoder. 7.7 kB gzip, zero dependencies, and the same behaviour in React, Vue, Svelte, a web component or a plain page.
What you get
A logo in the middle
Asked of the most used generator three separate times for 78 reactions, and never shipped. Here the modules underneath are removed, the error correction is raised to carry it, and an impossible logo is refused with the size that would work.
A budget that was measured
The published tolerances describe scattered damage. A logo is a solid square that destroys whole codewords, so the usable share is about a third of the headline figure — measured by covering codes until they stopped decoding.
Generate and scan, one package
Reading a code elsewhere means a second library last published in 2021. Scanning here uses the browser's own decoder, so the whole package is 7.7 kB against 57 and 108 kB of bundled decoder.
Verified, not assumed
1440 combinations of version, level, mode and mask are byte-identical to an independent implementation, and the error correction matches ISO/IEC 18004's own worked example.
Two paths, not two thousand
Most SVG output is a rectangle per dark module — over sixteen hundred elements at version 40. This is one path for the body and one for the finders, whatever the size.
Sharp wherever it lands
A fluid SVG that fills its box and prints at the printer's resolution, and a PNG rounded up to whole module pixels, because a fractional module width blurs the edges and fails to scan.
The camera parts that actually break
The back camera, playsinline for iOS, the torch, every camera listed, and the track stopped when the tab is hidden — the things behind 445 open issues on the scanner everyone uses.
Runs without a browser
No DOM in the encoder or the SVG renderer, so a server, a worker or a build step can produce codes — usually better than generating one in the browser on every view.
QR Code — overview
Why it exists
Twenty-four million downloads a week go to QR packages, and to generate and read a code you need two of them.
The generator — 19.1 million a week — has three dependencies, is CommonJS in
2026, and does not ship the browser build its own README tells you to use: its
most-reacted open issue, with 44 votes, is the TypeError that causes, and a
second with 30 says exactly why. The most-reacted feature request, asked three
separate times for a combined 78 reactions, is a logo in the middle. It has never
shipped.
The readers are worse. jsQR was last published in 2021 and freezes on iOS; html5-qrcode was last published in 2023 and has 445 open issues, including camera failures in webviews and iOS PWAs. Both are mostly bundled decoder — 57 kB and 108 kB gzipped, measured — for a job the browser now does itself.
What it does differently
- A logo that is checked. The modules under it are punched out rather than covered, the error-correction level is raised to carry it, and if even H is not enough you are told the largest size that is. The published tolerances describe scattered damage; a logo is a solid square that destroys whole codewords, so we measured the real budget by covering codes until they stopped decoding. It is about a third of the nominal figure.
- Generating and scanning in one 7.7 kB package, because the decoding is the browser's own and what we ship is the camera handling those 445 issues are actually about.
- Two SVG paths whatever the version, not sixteen hundred rectangles.
- Sharp at any size: a fluid SVG, and a PNG rounded up to whole module pixels because a fractional module width blurs the edges and fails to scan.
- Verified against the specification, not against hope: 1440 combinations byte-identical to an independent implementation, and 200 automatic-mask codes round-tripped through a third-party decoder.
Not in 1.0
A bundled decoder. Shipping 57 kB to every visitor so that Firefox can scan
is the trade the alternatives made, and it is why they are what they are. The
browser's own API covers Chrome on Android, Safari 17+, and Windows; canScan()
tells you when it does not, and the docs show the upload fallback. If a project
truly needs it everywhere, adding a decoder in that one branch costs the 57 kB
only where it is used.
Styled gradients and custom eye shapes beyond the three module shapes. They look good in a portfolio and are the most common cause of a code that scans on the designer's phone and nowhere else.
How it compares
Questions
Will a logo stop my code scanning?
Not if you let this decide. The modules under the logo are removed rather than covered, the error-correction level is raised to H if it needs to be, and if even that is not enough you get a warning naming the largest size that would work. The limit comes from measurement: codes at every level were covered by a growing square and decoded until they failed, and the safe share turned out to be about a third of the published tolerance.
Why does scanning not work in Firefox?
Because the decoding is the browser's own Barcode Detection API, which Firefox has not implemented. canScan() tells you, so you can offer a photo upload instead. The alternative is bundling a 57 kB decoder for everyone, which is most of why those libraries are the size they are — if you need it, add one in that single branch and pay for it only there.
SVG or PNG?
SVG for anything on a page or in print: it is two elements, it stays sharp at any size and it prints at the printer's resolution. PNG when something needs a file — toBlob() rounds the size up to whole module pixels, because a fractional module width blurs every edge and a blurred code is one a phone gives up on.
How do I fit more in?
Use upper case. HELLO WORLD is alphanumeric mode and fits a version 1 code; Hello World is byte mode and needs a bigger one. Numeric fits the most of all. Beyond that, shorten the URL — a QR code is not a good place for a long one, because every extra character makes the modules smaller and the scan harder.
Is the encoder actually correct?
Every matrix was compared against an independent implementation across 1440 combinations of version, level, mode and mask, and all are byte-identical. 200 codes with automatically chosen masks were round-tripped through a third-party decoder. The error-correction codewords match the worked example in ISO/IEC 18004, and the capacity and alignment tables match the published ones.