qr-code-styling, qr.js, qrcode.react, qrious, and react-qr-code are all JavaScript libraries designed to generate QR codes in web applications, but they differ significantly in rendering approach, framework integration, customization capabilities, and maintenance status. These packages enable developers to embed scannable QR codes into UIs for purposes like sharing URLs, authentication tokens, or contact information. While some focus on raw canvas or SVG output with minimal dependencies, others provide React-specific components with declarative APIs. Understanding their technical trade-offs is essential for choosing the right tool based on your project’s rendering needs, design requirements, and framework constraints.
Generating QR codes seems simple at first glance — just pass a string and get a square of black-and-white dots. But in real-world apps, you often need control over appearance, accessibility, performance, and framework integration. The five libraries under review take very different approaches. Let’s break them down by how they render, what you can customize, and how they fit into modern frontend workflows.
How a library draws the QR code affects everything from styling flexibility to accessibility.
qr-code-styling renders exclusively to SVG, giving you full vector control. This enables CSS styling, scaling without quality loss, and embedding of additional SVG elements like logos.
// qr-code-styling
import QRCodeStyling from "qr-code-styling";
const qrCode = new QRCodeStyling({
width: 300,
height: 300,
data: "https://example.com",
image: "/logo.svg", // embed logo
dotsOptions: { type: "rounded" }
});
qrCode.append(document.getElementById("qr-container"));
qr.js doesn’t render anything — it only generates the raw QR matrix (a 2D boolean array). You must build your own renderer.
// qr.js
import qr from "qr.js";
const qrcode = qr("https://example.com");
const matrix = qrcode.matrix; // [[true, false, ...], [...]]
// Now draw to canvas manually
const canvas = document.getElementById("qr-canvas");
const ctx = canvas.getContext("2d");
matrix.forEach((row, y) => {
row.forEach((cell, x) => {
ctx.fillStyle = cell ? "#000" : "#fff";
ctx.fillRect(x * 4, y * 4, 4, 4);
});
});
qrcode.react supports both <canvas> and <svg> via the renderAs prop. Canvas is default; SVG is better for SSR and accessibility.
// qrcode.react
import QRCode from "qrcode.react";
// Canvas (default)
<QRCode value="https://example.com" />
// SVG (for SSR/accessibility)
<QRCode value="https://example.com" renderAs="svg" />
qrious (deprecated) renders only to HTML5 <canvas>. No SVG option, which limits scalability and accessibility.
// qrious (avoid in new projects)
import QRious from "qrious";
new QRious({
element: document.getElementById("qr-canvas"),
value: "https://example.com",
size: 200
});
react-qr-code renders exclusively to accessible SVG, with built-in role="img" and support for title/aria-label.
// react-qr-code
import QRCode from "react-qr-code";
<QRCode
value="https://example.com"
size={256}
title="Scan to visit example.com"
aria-label="QR code for example.com"
/>
Need more than black-on-white? Here’s what each library supports.
qr-code-styling leads in visual customization:
square, rounded, dots, classy, etc.)// qr-code-styling: advanced styling
new QRCodeStyling({
data: "https://example.com",
dotsOptions: {
color: "#4A90E2",
type: "dots"
},
backgroundOptions: {
color: "#F5F7FA"
},
image: "/brand-logo.png",
imageOptions: { margin: 10 }
});
qrcode.react supports basic color and size adjustments:
fgColor / bgColorsizelevel)// qrcode.react: basic styling
<QRCode
value="https://example.com"
fgColor="#2D3748"
bgColor="#EDF2F7"
size={200}
/>
react-qr-code offers similar basic styling but with better accessibility hooks:
fgColor / bgColorsizetitle and aria-label for screen readers// react-qr-code: accessible styling
<QRCode
value="https://example.com"
fgColor="#1A202C"
bgColor="#FFFFFF"
size={220}
title="Example website QR code"
/>
qr.js provides zero built-in styling — you control every pixel when you implement your renderer.
qrious (deprecated) allowed basic color and size changes but nothing beyond that.
If you’re using React, how the library fits into your component model matters.
qrcode.react and react-qr-code are true React components — they re-render automatically when props change and work with React’s reconciliation.
// Both update when `url` changes
function App({ url }) {
return <QRCode value={url} />;
}
qr-code-styling is not React-native. You must manage its lifecycle imperatively (e.g., using useEffect and refs):
// qr-code-styling in React (manual)
import { useEffect, useRef } from "react";
import QRCodeStyling from "qr-code-styling";
function QRComponent({ value }) {
const ref = useRef();
const qrRef = useRef();
useEffect(() => {
if (!qrRef.current) {
qrRef.current = new QRCodeStyling({ data: value });
qrRef.current.append(ref.current);
} else {
qrRef.current.update({ data: value });
}
}, [value]);
return <div ref={ref} />;
}
qr.js and qrious also require manual DOM management in React and aren’t optimized for component re-renders.
For public-facing apps, QR codes should be accessible and work during server-side rendering.
react-qr-code shines here: SVG output includes role="img", supports title, and renders on the server.qrcode.react supports SSR only when renderAs="svg"; canvas won’t work on the server.qr-code-styling uses SVG but requires browser DOM APIs (document.createElementNS), so it won’t work during SSR — you’ll need dynamic imports or client-only rendering.qr.js and qrious produce canvas output that isn’t accessible by default and doesn’t support SSR.qriousAccording to its npm page and GitHub repo, qrious is deprecated and hasn’t been updated since 2017. It lacks TypeScript definitions, modern build tooling, and security updates. Do not use it in new projects.
| Package | Rendering | React Component | Customization | SSR Support | Accessibility | Status |
|---|---|---|---|---|---|---|
qr-code-styling | SVG | ❌ (imperative) | ✅✅✅ (logos, gradients) | ❌ | Medium | Active |
qr.js | Raw matrix | ❌ | ❌ (you build it) | ✅* | Low | Active |
qrcode.react | Canvas / SVG | ✅ | ✅ (colors, size) | ✅ (SVG only) | Medium | Active |
qrious | Canvas | ❌ | ✅ (basic) | ❌ | Low | Deprecated |
react-qr-code | SVG | ✅ | ✅ (colors, size, A11y) | ✅ | ✅✅ | Active |
* qr.js supports SSR only if you implement a server-safe renderer (e.g., using sharp or svg strings).
qr-code-styling, but wrap it properly in React and accept the SSR limitation.react-qr-code for best practices in accessibility and SSR, or qrcode.react if you prefer canvas fallbacks.qr.js and build your own renderer tailored to your exact needs.qrious? → Plan a migration to react-qr-code or qrcode.react.In most modern React applications, react-qr-code offers the best balance of simplicity, accessibility, and correctness, while qr-code-styling is the go-to for marketing or branded experiences where visual polish matters more than SSR.
Choose qr-code-styling if you need highly customizable, visually rich QR codes rendered as SVG with support for logos, rounded corners, gradient fills, and dot patterns. It works well in both vanilla JS and React (via ref or wrapper), but requires manual DOM handling in non-React contexts. Avoid it if you need lightweight output or server-side rendering, as it relies on browser DOM APIs and produces relatively large SVG markup.
Choose qr.js if you want a minimal, dependency-free, pure JavaScript library that generates raw QR code matrices without any built-in rendering. It’s ideal for projects that need full control over how and where the QR code is displayed (e.g., custom canvas drawing, terminal output, or integration with other graphics libraries). However, you’ll need to implement your own renderer, which adds development overhead.
Choose qrcode.react if you’re building a React application and need a simple, reliable component that renders QR codes to HTML <canvas> or <svg> with basic props like value, size, and bgColor. It’s actively maintained, supports SSR-friendly SVG mode, and integrates seamlessly with React’s lifecycle. Avoid it if you require advanced styling like custom dot shapes or embedded logos, as it offers limited visual customization.
Do not choose qrious for new projects — it is officially deprecated according to its npm page and GitHub repository, with no updates since 2017. While it once provided a straightforward canvas-based QR generator with basic color and size options, its outdated codebase lacks modern features, TypeScript support, and security patches. Migrate existing usage to alternatives like qrcode.react or react-qr-code.
Choose react-qr-code if you need a modern, lightweight React component that renders clean, accessible SVG QR codes with good defaults and essential customization (e.g., size, fgColor, bgColor, level). It’s ideal for projects prioritizing accessibility (includes role="img" and aria-label), SSR compatibility, and minimal bundle impact. Avoid it if you need complex visual enhancements like logos or non-standard dot shapes, as it focuses on simplicity and standards compliance.
JavaScript library for generating QR codes with a logo and styling.
Try it here https://qr-code-styling.com
If you have issues / suggestions / notes / questions, please open an issue or contact me. Let's create a cool library together.
If you would like to use additional stiles, you can connect extensions.
npm install qr-code-styling
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>QR Code Styling</title>
<script type="text/javascript" src="https://unpkg.com/qr-code-styling@1.5.0/lib/qr-code-styling.js"></script>
</head>
<body>
<div id="canvas"></div>
<script type="text/javascript">
const qrCode = new QRCodeStyling({
width: 300,
height: 300,
type: "svg",
data: "https://www.facebook.com/",
image: "https://upload.wikimedia.org/wikipedia/commons/5/51/Facebook_f_logo_%282019%29.svg",
dotsOptions: {
color: "#4267b2",
type: "rounded"
},
backgroundOptions: {
color: "#e9ebee",
},
imageOptions: {
crossOrigin: "anonymous",
margin: 20
}
});
qrCode.append(document.getElementById("canvas"));
qrCode.download({ name: "qr", extension: "svg" });
</script>
</body>
</html>
new QRCodeStyling(options) => QRCodeStyling
| Param | Type | Description |
|---|---|---|
| options | object | Init object |
options structure
| Property | Type | Default Value | Description |
|---|---|---|---|
| width | number | 300 | Size of canvas |
| height | number | 300 | Size of canvas |
| type | string ('canvas' 'svg') | canvas | The type of the element that will be rendered |
| shape | string (`'square' 'circle') | square | The shape of the qr-code, circle shape adds rundom extra dots arround |
| data | string | The data will be encoded to the QR code | |
| image | string | The image will be copied to the center of the QR code | |
| margin | number | 0 | Margin around canvas |
| qrOptions | object | Options will be passed to qrcode-generator lib | |
| imageOptions | object | Specific image options, details see below | |
| dotsOptions | object | Dots styling options | |
| cornersSquareOptions | object | Square in the corners styling options | |
| cornersDotOptions | object | Dots in the corners styling options | |
| backgroundOptions | object | QR background styling options | |
| nodeCanvas | node-canvas | Only specify when running on a node server for canvas type, please refer to node section below | |
| jsDom | jsdom | Only specify when running on a node server for svg type, please refer to node section below |
options.qrOptions structure
| Property | Type | Default Value |
|---|---|---|
| typeNumber | number (0 - 40) | 0 |
| mode | string ('Numeric' 'Alphanumeric' 'Byte' 'Kanji') | |
| errorCorrectionLevel | string ('L' 'M' 'Q' 'H') | 'Q' |
options.imageOptions structure
| Property | Type | Default Value | Description |
|---|---|---|---|
| hideBackgroundDots | boolean | true | Hide all dots covered by the image |
| imageSize | number | 0.4 | Coefficient of the image size. Not recommended to use ove 0.5. Lower is better |
| margin | number | 0 | Margin of the image in px |
| crossOrigin | string('anonymous' 'use-credentials') | Set "anonymous" if you want to download QR code from other origins. | |
| saveAsBlob | boolean | true | Saves image as base64 blob in svg type, see bellow |
When QR type is svg, the image may not load in certain applications as it is saved as a url, and some svg applications will not render url images for security reasons. Setting saveAsBlob to true will instead save the image as a blob, allowing it to render correctly in more places, but will also increase the file size.
options.dotsOptions structure
| Property | Type | Default Value | Description |
|---|---|---|---|
| color | string | '#000' | Color of QR dots |
| gradient | object | Gradient of QR dots | |
| type | string ('rounded' 'dots' 'classy' 'classy-rounded' 'square' 'extra-rounded') | 'square' | Style of QR dots |
| roundSize | boolean | true | Whether to round dots size to integer. true value might create extra margin around qr code. If false, shape-rendering="crispEdges" will be applied to SVG element. |
options.backgroundOptions structure
| Property | Type | Default Value |
|---|---|---|
| color | string ('#fff' 'rgb(255,255,255)' 'transparent') | '#fff' |
| gradient | object |
options.cornersSquareOptions structure
| Property | Type | Default Value | Description |
|---|---|---|---|
| color | string | Color of Corners Square | |
| gradient | object | Gradient of Corners Square | |
| type | string ('dot' 'square' 'extra-rounded' 'rounded' 'dots' 'classy' 'classy-rounded') | Style of Corners Square |
options.cornersDotOptions structure
| Property | Type | Default Value | Description |
|---|---|---|---|
| color | string | Color of Corners Dot | |
| gradient | object | Gradient of Corners Dot | |
| type | string ('dot' 'square' 'rounded' 'dots' 'classy' 'classy-rounded' 'extra-rounded') | Style of Corners Dot |
Gradient structure
options.dotsOptions.gradient
options.backgroundOptions.gradient
options.cornersSquareOptions.gradient
options.cornersDotOptions.gradient
| Property | Type | Default Value | Description |
|---|---|---|---|
| type | string ('linear' 'radial') | "linear" | Type of gradient spread |
| rotation | number | 0 | Rotation of gradient in radians (Math.PI === 180 degrees) |
| colorStops | array of objects | Gradient colors. Example [{ offset: 0, color: 'blue' }, { offset: 1, color: 'red' }] |
Gradient colorStops structure
options.dotsOptions.gradient.colorStops[]
options.backgroundOptions.gradient.colorStops[]
options.cornersSquareOptions.gradient.colorStops[]
options.cornersDotOptions.gradient.colorStops[]
| Property | Type | Default Value | Description |
|---|---|---|---|
| offset | number (0 - 1) | Position of color in gradient range | |
| color | string | Color of stop in gradient range |
QRCodeStyling.append(container) => void
| Param | Type | Description |
|---|---|---|
| container | DOM element | This container will be used for appending of the QR code |
QRCodeStyling.getRawData(extension) => Promise<Blob>
| Param | Type | Default Value | Description |
|---|---|---|---|
| extension | string ('png' 'jpeg' 'webp' 'svg') | 'png' | Blob type on browser, Buffer type on Node |
QRCodeStyling.update(options) => void
| Param | Type | Description |
|---|---|---|
| options | object | The same options as for initialization |
QRCodeStyling.applyExtension(extension) => void
| Param | Type | Description |
|---|---|---|
| extension | (svg, options) => void | Extension is a function that takes svg and previously applied options and modifies an svg |
applyExtension example
const extension = (svg, options) => {
const { width, height } = options;
const size = Math.min(width, height);
const border = document.createElementNS("http://www.w3.org/2000/svg", "rect");
const borderAttributes = {
"fill": "none",
"x": (width - size + 40) / 2,
"y": (height - size + 40) / 2,
"width": size - 40,
"height": size - 40,
"stroke": 'black',
"stroke-width": 40,
"rx": 100,
};
Object.keys(borderAttributes).forEach(attribute => {
border.setAttribute(attribute, borderAttributes[attribute]);
});
svg.appendChild(border);
};
QRCodeStyling.deleteExtension() => void
QRCodeStyling.download(downloadOptions) => Promise<void>
| Param | Type | Description |
|---|---|---|
| downloadOptions | object | Options with extension and name of file (not required) |
Promise returned will resolve into the data URI of the QR code image.
downloadOptions structure
| Property | Type | Default Value | Description |
|---|---|---|---|
| name | string | 'qr' | Name of the downloaded file |
| extension | string ('png' 'jpeg' 'webp' 'svg') | 'png' | File extension |
If you get an error running npm install referring to node-pre-gyp, this is caused by an attempt to compile the canvas dependency. See Compiling instructions in the README. For example on MacOS you need to install dependencies: brew install pkg-config cairo pango libpng jpeg giflib librsvg pixman.
You can use this on a node server by passing through the node-canvas or jsdom object depending if your creating a non-svg or svg respectively. You must pass both if using imageOptions.saveAsBlob.
Calling getRawData in node will return a Buffer instead of a Blob.
const { QRCodeStyling } = require("qr-code-styling/lib/qr-code-styling.common.js");
const nodeCanvas = require("canvas");
const { JSDOM } = require("jsdom");
const fs = require("fs");
const options = {
width: 300,
height: 300,
data: "https://www.facebook.com/",
image: "https://upload.wikimedia.org/wikipedia/commons/5/51/Facebook_f_logo_%282019%29.svg",
dotsOptions: {
color: "#4267b2",
type: "rounded"
},
backgroundOptions: {
color: "#e9ebee",
},
imageOptions: {
crossOrigin: "anonymous",
margin: 20
}
}
// For canvas type
const qrCodeImage = new QRCodeStyling({
jsdom: JSDOM, // this is required
nodeCanvas, // this is required,
...options,
imageOptions: {
saveAsBlob: true,
crossOrigin: "anonymous",
margin: 20
},
});
qrCodeImage.getRawData("png").then((buffer) => {
fs.writeFileSync("test.png", buffer);
});
// For svg type
const qrCodeSvg = new QRCodeStyling({
jsdom: JSDOM, // this is required
type: "svg",
...options
});
qrCodeSvg.getRawData("svg").then((buffer) => {
fs.writeFileSync("test.svg", buffer);
});
// For svg type with the inner-image saved as a blob
// (inner-image will render in more places but file will be larger)
const qrCodeSvgWithBlobImage = new QRCodeStyling({
jsdom: JSDOM, // this is required
nodeCanvas, // this is required
type: "svg",
...options,
imageOptions: {
saveAsBlob: true,
crossOrigin: "anonymous",
margin: 20
}
});
qrCodeSvgWithBlobImage.getRawData("svg").then((buffer) => {
fs.writeFileSync("test_blob.svg", buffer);
});
MIT License. Copyright (c) 2021 Denys Kozak