Library / SDK
educastellano/qr-code avatar
educastellano/qr-code

webcomponent-qr-code renders QR codes from a single HTML tag

Web Component for generating QR codes

575 stars83 forksJavaScriptMIT

At a glance

What is it?
webcomponent-qr-code is a JavaScript web component that turns a qr-code element into a QR code, with PNG, HTML table and SVG output, a shadow DOM you can style through ::part, and a configuration surface of six attributes. It is MIT licensed, published on npm as an ESM-only package since version 2.0.0 in June 2026.
Who is it for?
webcomponent-qr-code suits a frontend project that wants a QR code without a canvas library and without a build-time dependency, since the component ships as ESM from npm, renders into shadow DOM, and exposes its internals through ::part for styling. It is the wrong tool if you need to generate codes server side, and it is the wrong tool if your project still targets ES5 or expects a bundled dist file, since version 2.0.0 removed the legacy ES5 support and the dist bundle.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 119 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A QR code as a custom element

The project is a web component for generating QR codes, and the whole API in its simplest form is one HTML tag.

The package name is webcomponent-qr-code, the element is qr-code, and the payload goes in a data attribute:

html
<qr-code data="hello world!"></qr-code>

There is no canvas setup, no import of a rendering library in your own code, and no width and height to compute. The component takes the string, encodes it, and renders the result.

The repository is published on webcomponents.org, which is where the badge at the top of the README points, and the topics list names customelements, html, javascript, qr, qrcode and webcomponent. The primary language is JavaScript, the licence is MIT, and the author in package.json is Eduard Castellano.

The encoding itself comes from a library rather than from this project. The README describes the component as using a fork of the qr.js lib, and package.json records a single runtime dependency, qrjs at ^0.2.0. So the component is the web-facing layer: element lifecycle, attribute parsing, rendering formats and styling hooks, on top of an encoder it does not own.

There is also a live demo linked from the README, hosted at the project's GitHub Pages site, which is the fastest way to see what the three output formats look like before you install anything.

Three output formats and where each one breaks

The format attribute is the first option in the table, and it accepts png, html or svg, defaulting to png. The difference between them is not cosmetic, and the options table has two separate notes that tell you where the flexibility stops.

The PNG format is the default and produces an image element inside the shadow DOM, which the styling section of the README exposes as qr-code::part(img). That is the format to pick when the code needs to look like an image, scale cleanly, or sit alongside other raster assets.

The HTML format renders the QR code as a table, exposed as qr-code::part(table), and it is the one where the CSS unit options apply. The unit attribute sets the CSS units of the modulesize, defaulting to px, and the README marks it as supported for HTML generation only. The ratio attribute multiplies the modulesize, so the README's own example is that if units is rem and ratio is 0.0625, a modulesize of 5px is translated to 0.3125rem. That pairing exists so a code embedded in a rem-based layout keeps its size when the user changes their font size.

The SVG format renders vector output, exposed as qr-code::part(svg), and it was added in v0.1.7 back in April 2015.

So the practical rule is that the CSS-level sizing controls are HTML-only. If you select png or svg, the unit and ratio attributes do nothing, and you size the element yourself. That is stated in the table rather than hidden in the documentation, but it is easy to miss, and it is the kind of detail that produces a page where the QR code is the wrong size on a phone.

Error correction is the other option worth reading closely. The ecclevel attribute takes L, M, Q or H and defaults to L, and support for configuring it arrived in v1.3.0 in January 2025. Since L is the weakest level, a code rendered at the default carries less redundancy than one rendered at H, which matters for printed codes that will get scuffed or codes shown on a low contrast background.

Styling through the part pseudo-element

Because the component renders into shadow DOM, the ordinary CSS selectors you would use on a child element do not reach inside it. The README solves this with the part pseudo-element, and the three selectors map one to one onto the three formats.

css
/* format="png" */
qr-code::part(img) {}

/* format="html" */
qr-code::part(table) {}

/* format="svg" */
qr-code::part(svg) {}

That is a small design decision with real consequences. Support for custom styles with the ::part CSS pseudo-element arrived in v1.1.0 in November 2022, and it is what makes the component usable in a design system rather than only as a black box. With the parts exposed, a stylesheet can set a border, a background, a border radius or a margin on the rendered code without the component needing a configuration option for each.

It also constrains what you can do. The parts named here are img, table and svg, and a part selector only reaches the element it names, so styling the png output means targeting qr-code::part(img) and not the html one. There is no documented part for the element host itself, and the README does not describe a way to style the container.

Note that the shadow DOM boundary is also why the custom element name matters. A component that registers as qr-code claims that tag name in the document, which is exactly the kind of thing that collides with another library. The next section covers the escape hatch.

Registering a custom element name and importing the package

Installation is a single npm command:

sh
npm install webcomponent-qr-code

And importing the package for its side effect of registering the element:

js
import 'webcomponent-qr-code'

If you need a different tag name, the component class is exported from a subpath and you register it yourself:

js
import QRCode from 'webcomponent-qr-code/qr-code'

customElements.define('myapp-qrcode', QRCode)
html
<myapp-qrcode data="hello world!"></myapp-qrcode>

The package.json exports map shows the three entry points the component offers: the root for the side-effect import, ./qr-code and ./qr-code.js for the class itself, and ./package.json. The files array lists only index.js and qr-code.js, so the published package contains two source files and nothing else.

The custom element name support dates back to v1.0.0 in July 2018, which also moved the project to the modern customElements.define API and kept a legacy document.register() path in a separate qr-code.es5.js file. That legacy file is history now: version 2.0.0 removed legacy ES5 support, removed the build step and dev dependencies, removed the dist bundle, and publishes ESM only, with the package.json declaring type module. Version 2.0.0 also added a guard for custom element registration in non-browser environments, which is what stops the import from throwing when the module is pulled into a server-side rendering bundle or a test runner with no DOM.

That guard is the change that matters most for anyone upgrading. The old build step and the dist bundle are gone, so a project that consumed the bundled file needs to switch to an ESM import, and the removal of dev dependencies means the repository is now just the source files.

Where webcomponent-qr-code fits, and where it stops

The comparison worth making is with the two alternatives: a canvas-based QR library you drive yourself, and a hosted image service that returns a QR code as a URL.

Against a canvas library, the difference is that this component owns the rendering and the styling hooks for you. You do not compute a canvas size, you do not draw modules yourself, and you do not have to decide how the image scales in a responsive layout. The cost is that the options surface is small and closed. Seven attributes, no callback API, no hook for intercepting the encoded data, and no way to add your own encoding mode. If you need a QR code that encodes a structured payload your own code assembles, or one you need to export at print resolution, this component is not aiming at that.

Against a hosted image service, the difference is that everything happens in the browser. No request leaves the page, which matters when the encoded string is sensitive, when the page must work offline, or when a privacy policy does not allow third-party requests. The cost is that the code exists only as long as the page does, so it cannot be used for a server-generated document such as an invoice PDF or a poster, unless you render that on the client too.

The maintenance picture is straightforward. The last push was on 2026-06-26, the same day as the v2.0.0 changelog entry, the repository is not archived, and there are no GitHub releases, so the changelog in the README is the version history rather than the releases page. The default branch is gh-pages, which is where the live demo is served from, so the history of the demo site and the history of the source sit in the same branch.

The v2.0.0 entry is a substantial change rather than a version bump: ESM only, no dist bundle, no build step, no dev dependencies, and no ES5 support. For a component with a thirteen-year history, from the boilerplate-element start in September 2013 through the Polymer-free rewrite in March 2015, removing the old build path is a deliberate narrowing to one delivery model.

Editorial conclusion

webcomponent-qr-code suits a frontend project that wants a QR code without a canvas library and without a build-time dependency, since the component ships as ESM from npm, renders into shadow DOM, and exposes its internals through ::part for styling. It is the wrong tool if you need to generate codes server side, and it is the wrong tool if your project still targets ES5 or expects a bundled dist file, since version 2.0.0 removed the legacy ES5 support and the dist bundle. Before upgrading, read the changelog entry for v2.0.0, because the packaging changed from a bundled artifact to ESM only, and check the CSS unit options yourself, since the unit and ratio attributes are documented as supported for HTML generation only.

Frequently asked questions

How do I install webcomponent-qr-code?

Run npm install webcomponent-qr-code, then import the package for its side effect with import 'webcomponent-qr-code'. The package is published as ESM only since version 2.0.0, and the dist bundle and the build step were removed.

Which attributes does the qr-code element accept?

Seven: data for the encoded string, format for png, html or svg with png as the default, modulesize for the size of the modules in pixels defaulting to 5, margin in modules defaulting to 4, ecclevel taking L, M, Q or H defaulting to L, plus unit and ratio for CSS sizing, which the options table marks as supported for HTML generation only.

Can I style a QR code generated by webcomponent-qr-code?

Yes, through the part pseudo-element on the shadow DOM internals. The README shows qr-code::part(img) for png output, qr-code::part(table) for html output and qr-code::part(svg) for svg output, and support for this arrived in v1.1.0.

How do I use a different tag name for the element?

Import the class from the subpath and register it yourself with import QRCode from 'webcomponent-qr-code/qr-code' followed by customElements.define('myapp-qrcode', QRCode). The package.json exports map lists the root, ./qr-code and ./qr-code.js entry points.

What changed in webcomponent-qr-code 2.0.0?

The June 26, 2026 release guards custom element registration for non-browser environments, removes legacy ES5 support, removes the build step and dev dependencies, removes the dist bundle, and publishes ESM only.

Official sources

  1. educastellano/qr-code on GitHub
  2. Issues
  3. License: MIT
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/educastellano-qr-code.svg)](https://hysenlabs.com/projects/educastellano-qr-code)

Was this useful?