QR Code

A QR code encoded in the browser and drawn as SVG. No dependency and no network round trip — which matters, because the two things people encode most are a TOTP secret and a wallet address.

src/components/ui/qr-code.tsx

About

From the component's own source, where it was written.

A QR code, encoded here and drawn as SVG.

**Nothing leaves the page.** The common shortcut is an <img> pointed at a QR-as-a-service URL, which means the two things people most often encode — a TOTP enrolment secret and a wallet address — get handed to a third party and written into someone's access log. The encoder is @/lib/qr; see it for why that is worth 200 lines.

**SVG, not canvas.** A QR is a grid of squares, which is what vectors are for: it stays sharp at any size and when printed, it costs no raster memory at 512px, and it survives a server render. Canvas would need an effect, a ref, and a device-pixel-ratio dance to look right on a retina screen.

**The quiet zone is not decoration.** Four modules of margin are part of the spec, and a code butted against a coloured background is the single most common reason a QR will not scan. It is included by default and quietZone only makes it larger.

Contrast is the other scanning failure: dark must be darker than light, and inverting them (light code on a dark card) defeats most scanners. The defaults are near-black on white regardless of theme, deliberately — this is one of the few components that should not follow a dark mode.

Install

npx astralyx-ui add qr-code

Usage

import { QrCode } from '@/components/ui/qr-code'

<QrCode value="https://ui.astralyx.dev" level="M" size={160} />

Composer

Change the props and the source below follows.

QR code
Props
tsx
<QrCode
  value="https://ui.astralyx.dev"
  level="M"
  size={180}
/>

Examples

A TOTP enrolment code

Error correction levels

API

Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.

QrCode

Extends Omit<ComponentProps<'svg'>, 'children'>.

PropTypeDefaultDescription
valuestringrequiredEncoded in byte mode, versions 1–10 — 271 bytes at level L, 213 at M. Past that it throws rather than truncating a secret.
levelEccLevel'M'Higher recovers more damage and holds less. H is worth it when a logo covers the middle or it is printed on something that creases.
sizenumber160Rendered size in pixels. The SVG scales, so this is a hint, not a raster.
quietZonenumber4Four modules is the spec minimum, and a code butted against a coloured background is the most common reason a QR will not scan.
darkColorstring'#000000'Near-black on white regardless of theme, deliberately. Inverting them defeats most scanners.
lightColorstring'#ffffff'Near-black on white regardless of theme, deliberately. Inverting them defeats most scanners.
logoReact.ReactNode—Centred over the code. Needs level="H" to stay scannable.
logoSizenumber0.22Fraction of the code the logo may cover. Past ~0.25 it stops scanning.
labelstring'QR code'Accessible name. The encoded value is not read out — it is rarely useful.
fallbackReact.ReactNode—Rendered instead of the code when the value is too long to encode.
maskall eight scored—The real spec, not a fixed pattern. Scanners genuinely fail on a bad mask, and "mask 0 always" makes a code that reads on one phone and not another.

Behaviour

TopicDetailDescription
nothing leaves the pagethe whole pointThe usual shortcut is an <img> at a QR-as-a-service URL, which hands a TOTP secret or a payment address to a third party and writes it into their access log.
SVG, not canvasstays sharpSharp at any size and in print, no raster memory, and it survives a server render — canvas needs an effect, a ref and a device-pixel-ratio dance.