Skip to content
Son Tran (Snow)
All posts
  • Essay
  • #qrcode
  • #react
  • #open-source

@ttsalpha/qrcode: a zero-dependency QR code library for React

A pure-SVG QR code library for React and React Native, with the encoder written from scratch, no runtime dependencies, and styling that also works on the server.

@ttsalpha/qrcode is a QR code library for React and React Native. It draws pure SVG, has zero runtime dependencies, and the QR encoder is written from scratch against ISO/IEC 18004. It is on npm, the source is on GitHub, and there is a playground and docs site.

Why another QR library

The libraries I looked at each covered part of what I wanted. qrcode.react renders on the server but has no styling API. qr-code-styling has custom dots, colors and logos but relies on Canvas, so it breaks server-side. I wanted both at once: custom dot shapes, per-corner colors and a logo, as plain SVG that also renders in a Server Component.

Install and first render

pnpm add @ttsalpha/qrcode
import { QRCode } from '@ttsalpha/qrcode';

export default function App() {
  return <QRCode value="https://example.com" />;
}

React 18+ is the only peer dependency. react-dom is not needed.

Four entry points

The package is split so each environment only pulls what it can run:

Import fromContentsNeeds at runtime
@ttsalpha/qrcode<QRCode>, toSVGString, toDataURLReact 18+
@ttsalpha/qrcode/servertoSVGString, toDataURL, no client boundarynothing (toDataURL needs a browser)
@ttsalpha/qrcode/native<QRCode> for React NativeReact 18+, react-native-svg
@ttsalpha/qrcode/corebuildQR, toSVGStringnothing

The root entry is marked 'use client' because it ships the component. In a React Server Component, calling toSVGString() from it would throw, which is why /server exists with the same helpers and no client boundary.

Styling

There are three dot styles: square, circle, and rounded, where adjacent modules connect into one fluid shape. The finder patterns are styled independently, the inner block through corner.dot and the outer ring through corner.square, each with its own style and color.

<QRCode
  value="https://example.com"
  size={256}
  dotStyle="rounded"
  dotColor="#171717"
  corner={{
    square: { style: 'extra-rounded', color: '#14b8a6' },
  }}
/>

Logos without breaking the code

Put an image or any React element in the center with the logo prop. logo.size is a 0–1 scale relative to the largest safe logo, not a pixel size, and the error correction level is raised to match:

logo.sizeError correctionCleared width, at most
≤ 0.25L15% of the symbol
≤ 0.44M20%
≤ 0.69Q25%
≤ 1.00H30%
<QRCode
  value="https://example.com"
  dotStyle="rounded"
  logo={{ src: '/logo.png', size: 0.5, margin: 1, radius: 0.2 }}
/>

If you set qr.errorCorrectionLevel yourself, the logo is clamped to whatever that level can safely carry.

logo.element is rendered verbatim inside a <foreignObject>, so never pass unsanitised user input as element. javascript: and non-image data: URIs in src are rejected.

Use it on the server

toSVGString builds the SVG without React or the DOM, so it works in Server Components, route handlers, email templates and scripts. The output is deterministic, so results can be content-hashed and cached.

import { toSVGString } from '@ttsalpha/qrcode/server';

const svg = toSVGString({ value: 'https://example.com', size: 512 });

toDataURL renders a PNG or JPEG through Canvas, so it is browser-only.

React Native

@ttsalpha/qrcode/native draws the same symbols with react-native-svg. The package still has no runtime dependencies; react-native-svg is an optional peer dependency.

import { QRCode } from '@ttsalpha/qrcode/native';

<QRCode value="https://example.com" dotStyle="rounded" logo={{ src: require('./logo.png'), radius: 0.3 }} />;

It needs react-native-svg 14+ and React Native 0.75+.

Failing loudly

An empty value, or one too long to fit any version, throws a RangeError instead of rendering nothing. The ceiling is roughly 2,950 bytes at error correction level L and 1,270 at H. A throw during render unmounts the tree above it, so wrap the component in an error boundary when the value comes from user input.

How fast is it

I wrote a benchmark against qrcode.react, react-qr-code, qr-code-styling and qrcode (source: qrcode-benchmark). On ubuntu-24.04 with Node 24, median of 3 runs:

  • toSVGString reaches 7,817 renders per second, against 1,688 for qrcode.react and 86 for qr-code-styling
  • the React component renders at 3,950 per second
  • the cost is bundle size: 10.8 KB gzipped, against 5.9 KB for qrcode.react

It is a benchmark I wrote myself, so read the methodology on the page and run it before you trust the ratios.

Try it

If you just want a QR code without writing any code, the playground lets you style one and download it. Issues and ideas are welcome on GitHub.