Display
QR Code
A tokenized QR code renderer — encodes any string to SVG and inherits `currentColor`, so it auto-inverts in dark mode.
Preview in your theme
Loading preview…
Installation
npx shadcn@latest add https://ui.zyeon.ai/r/qr-code.jsonPrompt
The prompt behind this component — paste it into your AI assistant to recreate or adapt it.
Build a React + TypeScript + Tailwind "QrCode" component using the `qrcode`
npm package to generate SVG markup client-side.
Contract
- Export a forwardRef<HTMLDivElement, QrCodeProps> extending
React.HTMLAttributes<HTMLDivElement>.
- Props: value: string (required — the text/URL to encode), size?: number
(px, default 160, the full rendered box including safe-zone padding),
level?: "L" | "M" | "Q" | "H" (error-correction level, default "M"),
className.
Behavior
- Generate asynchronously with QRCode.toString(value, { type: "svg",
errorCorrectionLevel: level, margin: 0 }) inside a useEffect keyed on
[value, level]; resolve into state via .then, never setState synchronously
in the effect body itself.
- Guard against stale writes: flip a local `cancelled` flag true in the
effect's cleanup so a fast value/level change can't let an older pending
promise overwrite a newer result.
- On generation failure (value too long for the encodable capacity —
qrcode's promise rejects), catch it and render a small error placeholder
instead of a blank box; never let the rejection throw past the component.
- Re-generate whenever value or level changes — there is no caching, each
new value is a fresh async encode.
Rendering & styling
- Root: rounded-lg border bg-background p-3 text-foreground, fixed
width/height = size (the padding is the QR safe-zone, not additive) —
merge className via cn().
- Color tokenization (the differentiator vs a plain <img> QR): qrcode's SVG
renderer always emits exactly two hex colors — #000000 stroke for the dark
modules, #ffffff fill for the light background rect. String-replace
#000000 → currentColor and #ffffff → transparent before injecting, so the
mark inherits text-foreground (or whatever text-* an ancestor sets) and
the background rect disappears in favor of the container's own bg — dark
mode and custom accent colors work with zero extra color props.
- Inject the tokenized SVG through a single aria-hidden wrapper div with
dangerouslySetInnerHTML, sized [&_svg]:size-full so it fills the padded box.
- Accessibility: role="img" and aria-label={`QR code for ${value}`} on the
root container itself — never on the injected SVG, which stays aria-hidden.
Customization levers
- Fixed black/white for print or physical scanning: skip the tokenization
step (keep the raw #000000/#ffffff) when the code will be printed or
scanned against an arbitrary surface, where theme-matching contrast can't
be guaranteed to stay scannable.
- Center logo overlay: absolutely position a small <img>/<div> at the
container's center once level is "Q" or "H" — higher error correction
tolerates the obscured center modules; keep the logo under ~20% of size
or the symbol stops decoding.
- Download as PNG: swap toString(value, { type: "svg" }) for
QRCode.toDataURL(value, { errorCorrectionLevel: level }) to get a raster
PNG data URI, then wire it to an <a download href={dataUrl}> button — a
separate code path from the tokenized SVG preview, since a rasterized PNG
can't inherit currentColor.
- Density/robustness trade-off: level walks L → M → Q → H for more damage
tolerance at the cost of a denser pattern — pair higher levels with a
larger size so individual modules stay scannable.Concepts
- SVG currentColor tokenization — the generated markup only ever contains two hex colors; string-replacing them lets the mark inherit
text-foregroundand the container supply its own background, so dark mode and brand accents need zero extra props. - Async generate-to-state — encoding runs in
QRCode.toString(...).then(...)inside an effect, never a synchronoussetStatein the effect body. - Stale-response guard — a
cancelledflag set in the effect cleanup stops an in-flight promise from a previousvalue/levelfrom overwriting a newer result. - Error-correction trade-off —
level(L→H) trades pattern density for tolerance to damage or a center logo overlay; it is not a visual style knob. - Safe-zone via padding — the quiet zone a scanner needs around the mark comes from the container's own
p-3, not from the encoder'smarginoption, so it composes withsizepredictably.