- 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/qrcodeimport { 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 from | Contents | Needs at runtime |
|---|---|---|
@ttsalpha/qrcode | <QRCode>, toSVGString, toDataURL | React 18+ |
@ttsalpha/qrcode/server | toSVGString, toDataURL, no client boundary | nothing (toDataURL needs a browser) |
@ttsalpha/qrcode/native | <QRCode> for React Native | React 18+, react-native-svg |
@ttsalpha/qrcode/core | buildQR, toSVGString | nothing |
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.size | Error correction | Cleared width, at most |
|---|---|---|
≤ 0.25 | L | 15% of the symbol |
≤ 0.44 | M | 20% |
≤ 0.69 | Q | 25% |
≤ 1.00 | H | 30% |
<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:
toSVGStringreaches 7,817 renders per second, against 1,688 forqrcode.reactand 86 forqr-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.