QR code component rendered as SVG
A server-safe React component that turns text into a crisp QR code SVG with a quiet zone, colours and error correction.
React / Next.jsUtilitiesreactqr-codesvgcomponent
Code
import { useMemo } from 'react';
import QRCode from 'qrcode';
type Props = {
value: string;
size?: number; // rendered width/height in px
level?: 'L' | 'M' | 'Q' | 'H'; // error correction
margin?: number; // quiet zone in modules (spec says 4)
fg?: string;
bg?: string;
label?: string;
};
export function QrCode({ value, size = 160, level = 'M', margin = 4, fg = '#000', bg = '#fff', label }: Props) {
const { d, span } = useMemo(() => {
const qr = QRCode.create(value, { errorCorrectionLevel: level });
const n = qr.modules.size;
const bits = qr.modules.data; // flat 0/1 array, row by row
let path = '';
for (let i = 0; i < bits.length; i++) {
if (bits[i]) {
const x = (i % n) + margin;
const y = Math.floor(i / n) + margin;
path += `M${x} ${y}h1v1h-1z`; // one dark module
}
}
return { d: path, span: n + margin * 2 };
}, [value, level, margin]);
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox={`0 0 ${span} ${span}`}
width={size}
height={size}
role="img"
aria-label={label ?? `QR code for ${value}`}
shapeRendering="crispEdges"
>
<rect width={span} height={span} fill={bg} />
<path d={d} fill={fg} />
</svg>
);
}
Needs: qrcode@^1.5.4, @types/qrcode@^1.5.5
How it works
What it solves
Canvas-based QR images blur when scaled, cannot be styled with CSS and need a client component. Rendering the module matrix as one SVG path gives a razor-sharp code at any size, prints well, and works in server components with no hydration cost.
How it works
QRCode.create from the qrcode package returns the module matrix without drawing anything: a flat bit array and a size. The component walks that array and, for every dark module, appends a tiny square sub-path of the form M x y h1 v1 h-1 z to a single d string. One <path> with thousands of squares is far lighter than thousands of <rect> elements.
The viewBox is the matrix size plus a margin on each side. The standard asks for a quiet zone of four modules; scanners rely on that blank border, so the margin defaults to four. Using shape-rendering="crispEdges" prevents hairline seams between neighbouring squares. The text is encoded in a useMemo, so re-renders with the same value cost nothing, and a role="img" plus aria-label makes the code accessible.
Adapting it
- Higher error correction (
H) tolerates a logo overlay but makes the code denser. - Convert the SVG to PNG for downloads by drawing it onto a canvas.
- Add rounded dots by replacing the square sub-path with circles.
Pitfalls
- Keep dark-on-light contrast high; inverted codes fail on many scanners.
- Long URLs create dense codes that are hard to scan; shorten them first.
How to use it
- Run
npm i qrcodeandnpm i -D @types/qrcode. - Save as
components/QrCode.tsx; it works in server and client components. <QrCode value="https://example.com/menu" size={192} />.- Use
level="H"when overlaying a logo in the centre.
Licence: DEV3 Snippet Licence. Free to use, change and ship in personal and commercial projects. Attribution is appreciated, not required. Please do not redistribute snippets as a collection.