DEV3 Technologies home

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

  1. Run npm i qrcode and npm i -D @types/qrcode.
  2. Save as components/QrCode.tsx; it works in server and client components.
  3. <QrCode value="https://example.com/menu" size={192} />.
  4. 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.

Related snippets