QR Code React component for Tailwind CSS v4
v1.0.0Added in 1.0.0
A React QR code generator with rounded or dotted modules, soft eyes, a center logo, any colors, a caption and one-tap PNG or SVG export.
Point a phone camera at it: the code stays dark on a light tile in both themes, so it scans from a screen or a print. Changing the text crossfades to the new code; the first code springs in once (instantly with reduced motion). With downloadable on, Download opens a menu (a bottom sheet on phones) with a PNG at exportSize, an SVG and, where the device can share files, Share which opens the phone's share sheet; Copy image puts the PNG on the clipboard and turns into Copied for a moment. With showValue on, the encoded text sits under the caption with a copy button (a tooltip names it). Tab reaches the buttons in order, Enter or Space opens the menu, arrows move through it and Escape closes it back onto the trigger. Screen readers read the code as an image named after its text, and hear every copy and download.
QR Code is a Pro React component in the React media components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add qr-code, together with Button, Dropdown Menu, Skeleton, Tooltip, useClipboard and 3 more and the npm packages qrcode, motion and lucide-react.
Add to a project
Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add qr-codeQR Code is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans
Usage
Import QrCode from @/components/ui/qr-code.
import { QrCode } from "@/components/ui/qr-code"
export function PayInvoice() {
return (
<QrCode
value="https://pay.acme.com/invoice/2026-0412?amount=3025.00"
caption="Scan to pay $3,025.00"
logo="/logo.svg"
downloadable
fileName="Invoice 2026-0412"
labels={{ menuTitle: "Save the payment code" }}
/>
)
}Props
value*default"https://acme.com/i/2026-0412"stringThe encoded text: a url, a Wi-Fi string (WIFI:T:WPA;S:Acme;P:...;;), a payment string. Any Unicode works (byte mode). An empty string shows the empty tile, text too long for the level shows the error tile; nothing throws.
captiondefault""ReactNodeA line under the code, like Scan to pay. On the tile it takes the module color; unframed or on a transparent tile it uses the page text color.
showValuedefaultfalsebooleanShows the encoded text under the caption in mono, truncated, with a copy button.
logoReactNode | stringAn image url or a node (an inline svg mark, an <img>) centered on a cleared tile. It raises the error correction to Q, or H when logoSize is above 0.2. Exports embed url logos that can be fetched (same origin, CORS or data urls) and inline svg nodes.
sizedefault192numberWidth of the code in px (the frame adds 12px padding). It never grows past its container. Clamped so every module is at least 1.5px, with a warning in development.
moduleStyledefault"rounded""square" | "rounded" | "dots"square: classic crisp squares. rounded: neighbouring modules merge into pills and blobs, corners round only where no neighbour touches. dots: circles at 85%.
finderStyledefault"rounded""square" | "rounded" | "circle"The three corner eyes: an outer ring and an inner square, both in this shape.
colordefault"#111111"stringModule color. It stays dark in dark mode on purpose: scanners need dark modules on a light background.
backgrounddefault"#ffffff"stringThe tile and the quiet zone. "transparent" is allowed; the contrast is then up to you.
finderColordefault""stringThe eyes in a second color, like the brand orange. Empty uses the module color.
frameddefaulttruebooleanThe tile: background, 12px padding and a raised shadow, so the code reads well on a dark page. Off draws the bare code.
radiusdefault"md""none" | "sm" | "md" | "lg" | "full"Corners of the tile: 0 / 12 / 16 / 24 / 32px. Unframed codes, the loading and empty tiles use a smaller matching step.
logoSizedefault0.22numberWidth of the logo tile as a fraction of the code, clamped to 0.1 to 0.3.
excavatedefaulttruebooleanClears the modules under the logo plus at least half a module around it. Off lays the logo tile over them.
leveldefault"M""L" | "M" | "Q" | "H"Error correction: L recovers 7% of damage, M 15%, Q 25%, H 30%. Higher levels mean denser codes. With a logo it is raised to at least Q (H when the logo is wider than 20%); data-level shows the level in use.
margindefault2numberQuiet zone in modules, drawn in the background color inside the frame. 4 is the ISO minimum for print without a frame.
downloadabledefaultfalsebooleanShows the actions row: a Download menu (PNG, SVG, Share where files can be shared) and Copy image where the clipboard takes images.
fileNamedefault"qr-code"stringBase name of exported files, passed to timestampedFileName: Invoice-2026-0412 becomes Invoice-2026-0412-2026-09-24-1640.png.
exportSizedefault1024numberWidth and height of exported PNGs in px (the SVG export keeps the display size and scales freely).
loadingdefaultfalsebooleanA skeleton at the final size while the value is on its way; the code springs in when it arrives.
animateddefaulttruebooleanThe entrance spring the first time a code appears. Value changes always crossfade; reduced motion turns both into a short fade.
setApi(api: QrCodeApi) => voidReceives { toSvgString(), toBlob({ size, type, quality, background }), download("png" | "svg"), copy() } once after mount, for your own export buttons. Call copy() straight from a click handler (Safari).
onExport(file: { action: "download" | "share" | "copy"; format: "png" | "svg"; fileName: string; blob: Blob }) => voidCalled after every finished download, share or image copy, from the built-in actions and the api. The playground uses it for its last export readout.
labelsPartial<QrCodeLabels>Every visible and announced string: image(value), download, png, pngHint(size), svg, svgHint, share, copyImage, copied, copyFailed, copyValue, downloaded(fileName), exportFailed, menuTitle, cancel, tooLong, empty. English defaults in DEFAULT_QR_CODE_LABELS.
classNamesPartial<Record<"frame" | "svg" | "logo" | "caption" | "value" | "actions", string>>Extra classes for the inner parts.
Parts
qrToSvgString
functionThe same code as standalone SVG text without a DOM: for emails, PDFs and server routes. Options mirror the props (level, size, margin, moduleStyle, finderStyle, color, background, finderColor, logo as a url or svg markup, logoSize, excavate, title). Signature: (value: string, options?: QrSvgOptions) => string.
getQrMatrix
functionThe raw module grid from the qrcode package, for custom renderers. Throws when the value does not fit. Signature: (value: string, level?: QrCodeLevel) => { size: number; get(row: number, col: number): boolean }.
buildQrGeometry
functionThe path data the component draws (modules, the three finders, the logo box), in module units. Signature: (matrix: QrMatrix, options?: { margin; moduleStyle; finderStyle; logoSize: number | null; excavate }) => QrGeometry.
effectiveQrLevel
functionThe level a logo raises the code to: at least Q, H when the logo is wider than 20%. Signature: (level: QrCodeLevel, logoSize: number | null) => QrCodeLevel.
DEFAULT_QR_CODE_LABELS
constantThe English strings for the image name, the export menu (PNG, SVG, share, copy image, copy text), the toasts and the empty and too long states.
QrCodeApi
typeThe type of the object setApi receives: toSvgString, toBlob, download and copy.
Examples
Module and finder styles
square, rounded and dots modules with square, rounded and circle eyes.
Office Wi-Fi
A WIFI: string joins the network from the camera app; the password stays copyable.
Custom colors
Green modules with orange eyes on cream, and a transparent unframed code on a brand card.
Your own export buttons
setApi hands you toBlob, toSvgString, download and copy.
Loading, then the code arrives
A skeleton at the final size during a 1.4 s request, then the code springs in.
Error correction
The same url at L, M, Q and H: more recovery, denser grid.
Empty, too long, loading
No value, more text than a code holds, and a pending value. Nothing throws.
Dependencies
Files
- components/ui/qr-code.tsx
FAQ
Is the React QR Code component free?
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
How do I install QR Code?
Run npx wingo-ui@latest add qr-code. It also adds the Wingo UI items it builds on (Button, Dropdown Menu, Skeleton, Tooltip, useClipboard and 3 more) and the npm packages qrcode, motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/qr-code.
How does QR Code behave on phones?
With downloadable on, Download opens a menu (a bottom sheet on phones) with a PNG at exportSize, an SVG and, where the device can share files, Share which opens the phone's share sheet; Copy image puts the PNG on the clipboard and turns into Copied for a moment.
See also
Used in
More React media components
Articles using this component
- Mobile App Landing Page Template: QR Code and Store Links
Build it: screens and apps9 min read
- React QR Code with Logo: Rounded Modules, PNG and SVG
Component guides9 min read