{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "qr-code",
  "title": "QR Code",
  "description": "A scannable QR code, drawn as SVG, for putting a link, a Wi-Fi network or a 2FA secret on screen where a phone can pick it up. Reach for it wherever something has to get from this screen onto someone's device — a two-factor enrolment screen, a Wi-Fi guest network on a poster or a hotel card, an event ticket, a boarding pass or a venue check-in, a payment or invoice link, a table ordering code, a menu, a device or kiosk pairing step, an app-store or download link, an invite or referral link, a shipping or returns label, a warranty or product registration, a business card or conference badge, a receipt, a signup link on a slide, and any hand-off from a desktop app to the phone in someone's pocket. Common asks it answers: \"qr code react\", \"react qr code component\", \"generate qr code react\", \"shadcn qr code\", \"qr code generator react\", \"qrcode.react alternative\", \"react-qr-code alternative\", \"qrcode npm alternative\", \"qr code without dependencies\", \"qr code no npm package\", \"qr code svg react\", \"qr code canvas vs svg\", \"qr code server component\", \"qr code next.js app router\", \"qr code ssr\", \"wifi qr code generator\", \"share wifi password qr code\", \"WIFI: qr format\", \"otpauth qr code\", \"2fa qr code react\", \"totp enrollment qr\", \"google authenticator qr code\", \"qr code not scanning\", \"qr code won't scan\", \"qr code scans on some phones\", \"quiet zone qr code\", \"qr code margin\", \"qr code dark mode\", \"inverted qr code not scanning\", \"white qr code on dark background\", \"qr code error correction level\", \"qr code with logo in middle\", \"qr code capacity limit\", \"qr code too long\", \"qr code changes size when text changes\", \"qr code utf-8\", \"qr code japanese characters\", \"qr code emoji\", \"qr code blurry\", \"qr code print quality\", \"qr code accessibility\", \"qr code screen reader\", \"qr code aria-label\", \"QRコード react\", \"QR コード 生成\", \"二次元コード コンポーネント\", \"WiFi QRコード\". Official shadcn/ui has nothing here and the measurement is not close: fetching every entry in its registry today — 63 listed, 62 fetchable, questionnaire is indexed and 404s on both style tracks — and searching all 245,417 bytes of it, the strings qr, QR, Reed, Galois, errorCorrection, quietZone, finderPattern, alignmentPattern, shapeRendering, crispEdges, otpauth, wifi, barcode and scanner are every one of them a zero hit. So this gets solved by adding a package, and that is the first thing worth knowing about this one: the encoder is in the file. Three packing modes chosen by content, all four correction levels, versions 1 to 40, Reed–Solomon over GF(256), block interleaving, and all eight masks scored — no dependencies at all, not even an icon, in a registry whose 83 other components have never needed more than lucide-react. Because a QR encoder's bugs are invisible, correctness here is measured rather than asserted. Every grid this ships was rendered to a PNG and read back by Apple's Vision framework, an independent decoder sharing no code with it: 426 codes on 2026-09-12, covering all 40 versions at all four levels, every mask forced, the mode and capacity boundaries, UTF-8 and emoji, a version 40 code at its 2,953-byte maximum, and 240 random payloads at the shipped defaults, each decoded string compared byte for byte with its input. That sweep found a real bug — one error-correction table was a row short from version 32 up, so nine versions produced codes that rendered perfectly and decoded to nothing — and the verified grids are frozen in the test suite so it cannot come back. Beyond the encoding, three things decide whether a code actually works. The first is the quiet zone: a scanner finds a code by finding its border, so four light modules of margin are not decoration, and a code butted against other content renders beautifully and is simply never seen. Nothing catches that except pointing a real camera at it, so the default is the required value and the light ground is drawn as part of the SVG rather than left to whatever is behind it. The second is colour, which is functional rather than decorative and therefore does not follow your theme by default: dark modules on a light ground is what the format specifies and what decoders assume, inverted codes are read by some phones, fewer cheap scanners and no printer at all. So the code stays black on white in dark mode — which is what a boarding pass, a wallet app and a bank statement all do — and moduleClassName and backgroundClassName are there when you have tested otherwise. The third is SVG rather than canvas: a canvas is a bitmap that resamples badly the moment it is scaled or printed, and to anything that is not an eye it is a blank rectangle. This carries role=\"img\" and a label, and deliberately does not read the payload aloud unless you ask — sixty characters of URL letter by letter helps nobody, and the two things most often in a code are a Wi-Fi password and a 2FA secret. A QR is something you point a second device at, so put the payload on the page as well: a real link, or a code beside a copy button. It has no hooks, no state and no effects, so there is no \"use client\" on it: it renders on the server and ships zero JavaScript for what is a static picture, with a small internal cache keeping a re-rendering parent from re-encoding. errorCorrection defaults to M and is boosted automatically to the strongest level that still fits the same version, since the slack would otherwise be spent on padding — the code is the same size and survives more damage. minVersion holds the size still for a payload that changes while it is on screen, like a rotating token, which would otherwise resize the code mid-scan. A payload too long to fit throws from encodeQr — a truncated code still scans and hands back the wrong string — while the component renders a fallback instead of taking the layout down. Two payload builders come with it, both for the traps rather than the strings: wifiPayload escapes the ; : , and backslash that a generated password contains and that otherwise truncate the network details silently, and quotes an all-hex value so it is not read as a raw key; otpauthUri puts the issuer in both the label prefix and the query parameter, because different authenticator apps read different ones, and escapes the two halves of the label separately so the colon separator survives while a + or a space in the address does not break it. encodeQr and qrPath are exported for a code you want to rasterise on a server, put on a label, or draw yourself. Within pulld it completes the two-factor setup screen alongside otp-input and recovery-codes, and sits beside copy-button and copy-field, which are what you put next to it for everyone who cannot scan it.",
  "files": [
    {
      "path": "registry/ui/qr-code.tsx",
      "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/**\n * How much of the code can be destroyed and still read: roughly 7%, 15%, 25% and 30%.\n *\n * The instinct is that higher is simply better. It is not free — the correction lives in the same\n * grid as the data, so raising the level on a payload that already nearly fills a version pushes it\n * to a larger one, and a larger version means smaller modules at the same printed size, which is\n * itself a reason codes fail to scan. `\"M\"` is the working default. Go to `\"Q\"` or `\"H\"` only when\n * something is going to happen to the code — a logo dropped in the middle, a label that will be\n * handled, a screen that will be photographed at an angle — and let it grow.\n */\nexport type ErrorCorrectionLevel = \"L\" | \"M\" | \"Q\" | \"H\"\n\n/** The three ways a payload can be packed. Chosen for you; exposed because the result names it. */\nexport type QrMode = \"numeric\" | \"alphanumeric\" | \"byte\"\n\n/** A finished code: the grid, and the parameters that produced it. */\nexport interface QrMatrix {\n  /** Modules per side, always `4 * version + 17`. Excludes the quiet zone. */\n  size: number\n  /** 1–40. Larger holds more and draws finer. */\n  version: number\n  /** The level actually used, which `boost` may have raised above the one requested. */\n  errorCorrection: ErrorCorrectionLevel\n  /** 0–7. The pattern XORed over the data to break up runs and blocks. */\n  mask: number\n  /** How the payload was packed. */\n  mode: QrMode\n  /** `modules[y][x]` — true is a dark module. Row-major, origin top-left. */\n  modules: boolean[][]\n}\n\n// --- the spec's two tables -------------------------------------------------------------------------\n//\n// Everything else about a version is computed below rather than tabulated. These two cannot be: they\n// are the block structure chosen by ISO/IEC 18004 for each version and level, and they are simply\n// facts to be looked up. Index 0 is padding so that the version can index directly.\n\nconst EC_CODEWORDS_PER_BLOCK: Record<ErrorCorrectionLevel, readonly number[]> = {\n  //  0  1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36  37  38  39  40\n  L: [0, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30],\n  M: [0, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28],\n  Q: [0, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30],\n  H: [0, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30],\n}\n\nconst EC_BLOCKS: Record<ErrorCorrectionLevel, readonly number[]> = {\n  //  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40\n  L: [0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25],\n  M: [0, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49],\n  Q: [0, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68],\n  H: [0, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81],\n}\n\n/** The five bits `aria`-invisible to everything but a scanner: the level's own code in the format info. */\nconst FORMAT_EC_BITS: Record<ErrorCorrectionLevel, number> = { L: 1, M: 0, Q: 3, H: 2 }\n\nconst EC_LEVELS: readonly ErrorCorrectionLevel[] = [\"L\", \"M\", \"Q\", \"H\"]\n\nexport const MIN_QR_VERSION = 1\nexport const MAX_QR_VERSION = 40\n\n/** The 45 characters alphanumeric mode can pack two-to-eleven-bits. Note: upper case only. */\nconst ALPHANUMERIC = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:\"\n\n/**\n * Modules in the grid that are not function patterns, before error correction is taken out.\n *\n * Computed rather than tabulated. The area is the square, less the three finder patterns with their\n * separators and the format information beside them, less the two timing lines, less the alignment\n * patterns (which overlap the timing lines, hence the correction), less the version blocks from 7 up.\n */\nfunction rawDataModules(version: number): number {\n  let result = (16 * version + 128) * version + 64\n  if (version >= 2) {\n    const align = Math.floor(version / 7) + 2\n    result -= (25 * align - 10) * align - 55\n    if (version >= 7) result -= 36\n  }\n  return result\n}\n\n/** Total codewords in the version, data and error correction together. */\nfunction totalCodewords(version: number): number {\n  return Math.floor(rawDataModules(version) / 8)\n}\n\n/** Codewords left for the payload once error correction has taken its share. */\nfunction dataCodewords(version: number, ecl: ErrorCorrectionLevel): number {\n  return totalCodewords(version) - EC_CODEWORDS_PER_BLOCK[ecl][version] * EC_BLOCKS[ecl][version]\n}\n\n/**\n * Where the alignment patterns go, as coordinates that apply to both axes.\n *\n * The spec prints this as a table of forty rows and it is reproducible as arithmetic, with exactly\n * one exception at version 32 — which is why the exception is written out rather than smoothed over:\n * every generator that \"simplified\" the formula and dropped it produces a version 32 code that no\n * reader will touch, and version 32 is rare enough that nobody notices until a payload happens to\n * land on it.\n */\nfunction alignmentPositions(version: number): number[] {\n  if (version === 1) return []\n  const count = Math.floor(version / 7) + 2\n  const size = version * 4 + 17\n  const step = version === 32 ? 26 : Math.ceil((version * 4 + 4) / (count * 2 - 2)) * 2\n  const positions = [6]\n  for (let pos = size - 7; positions.length < count; pos -= step) positions.splice(1, 0, pos)\n  return positions.sort((a, b) => a - b)\n}\n\n// --- GF(256) and Reed–Solomon ------------------------------------------------------------------------\n//\n// The error correction is a Reed–Solomon code over the field with 256 elements, which is what lets a\n// scanner read a code through a thumb, a crease or a coffee ring. Bytes are field elements; addition\n// is XOR; multiplication is carry-less multiplication reduced modulo the field's primitive polynomial\n// x^8 + x^4 + x^3 + x^2 + 1.\n\nfunction gfMultiply(x: number, y: number): number {\n  let z = 0\n  for (let i = 7; i >= 0; i--) {\n    // Double, and fold back into the field whenever doubling has pushed a bit out of the top.\n    z = (z << 1) ^ ((z >>> 7) * 0x11d)\n    z ^= ((y >>> i) & 1) * x\n  }\n  return z & 0xff\n}\n\n/** Coefficients of the divisor polynomial for `degree` correction bytes, highest power first. */\nfunction generatorPolynomial(degree: number): Uint8Array {\n  // Starts as the constant 1 and is multiplied by (x - r) for successive powers r of the generator\n  // element 2. Stored without its leading coefficient, which is always 1.\n  const result = new Uint8Array(degree)\n  result[degree - 1] = 1\n  let root = 1\n  for (let i = 0; i < degree; i++) {\n    for (let j = 0; j < degree; j++) {\n      result[j] = gfMultiply(result[j], root)\n      if (j + 1 < degree) result[j] ^= result[j + 1]\n    }\n    root = gfMultiply(root, 2)\n  }\n  return result\n}\n\n/** The correction bytes for one block: the remainder of the data divided by the generator. */\nfunction remainderBytes(data: Uint8Array, generator: Uint8Array): Uint8Array {\n  const result = new Uint8Array(generator.length)\n  for (const byte of data) {\n    const factor = byte ^ result[0]\n    result.copyWithin(0, 1)\n    result[result.length - 1] = 0\n    for (let i = 0; i < result.length; i++) result[i] ^= gfMultiply(generator[i], factor)\n  }\n  return result\n}\n\n// --- packing the payload -----------------------------------------------------------------------------\n\nfunction isNumeric(value: string): boolean {\n  for (let i = 0; i < value.length; i++) {\n    const code = value.charCodeAt(i)\n    if (code < 48 || code > 57) return false\n  }\n  return value.length > 0\n}\n\nfunction isAlphanumeric(value: string): boolean {\n  for (const char of value) if (!ALPHANUMERIC.includes(char)) return false\n  return value.length > 0\n}\n\n/**\n * The mode that packs `value` smallest.\n *\n * Numeric fits three digits into ten bits and alphanumeric two characters into eleven, against eight\n * bits per byte otherwise — so a phone number or a ticket reference of the same length can land two\n * or three versions lower, which is the difference between a code that scans across a room and one\n * that does not. The catch, and the reason a \"this looks alphanumeric\" guess written by hand keeps\n * producing unreadable codes: alphanumeric mode has no lower case and only nine punctuation marks.\n * `HTTPS://EXAMPLE.COM` qualifies and `https://example.com` does not, so nearly every URL is byte\n * mode and must be.\n */\nfunction pickMode(value: string): QrMode {\n  if (isNumeric(value)) return \"numeric\"\n  if (isAlphanumeric(value)) return \"alphanumeric\"\n  return \"byte\"\n}\n\n/** Bits in the character-count field, which widens twice as versions grow. */\nfunction countBits(mode: QrMode, version: number): number {\n  const tier = version <= 9 ? 0 : version <= 26 ? 1 : 2\n  if (mode === \"numeric\") return [10, 12, 14][tier]\n  if (mode === \"alphanumeric\") return [9, 11, 13][tier]\n  return [8, 16, 16][tier]\n}\n\n/** The unit `countBits` counts: digits, characters, or bytes after UTF-8 encoding. */\nfunction unitCount(mode: QrMode, value: string, bytes: Uint8Array): number {\n  return mode === \"byte\" ? bytes.length : value.length\n}\n\nfunction payloadBits(mode: QrMode, count: number): number {\n  if (mode === \"numeric\") return 10 * Math.floor(count / 3) + [0, 4, 7][count % 3]\n  if (mode === \"alphanumeric\") return 11 * Math.floor(count / 2) + 6 * (count % 2)\n  return 8 * count\n}\n\nclass BitBuffer {\n  readonly bits: number[] = []\n\n  append(value: number, length: number): void {\n    for (let i = length - 1; i >= 0; i--) this.bits.push((value >>> i) & 1)\n  }\n}\n\nfunction writeSegment(buffer: BitBuffer, mode: QrMode, value: string, bytes: Uint8Array): void {\n  if (mode === \"numeric\") {\n    for (let i = 0; i < value.length; i += 3) {\n      const chunk = value.slice(i, i + 3)\n      buffer.append(Number(chunk), chunk.length * 3 + 1)\n    }\n    return\n  }\n  if (mode === \"alphanumeric\") {\n    let i = 0\n    for (; i + 2 <= value.length; i += 2) {\n      buffer.append(ALPHANUMERIC.indexOf(value[i]) * 45 + ALPHANUMERIC.indexOf(value[i + 1]), 11)\n    }\n    if (i < value.length) buffer.append(ALPHANUMERIC.indexOf(value[i]), 6)\n    return\n  }\n  for (const byte of bytes) buffer.append(byte, 8)\n}\n\nconst MODE_INDICATOR: Record<QrMode, number> = { numeric: 1, alphanumeric: 2, byte: 4 }\n\n/**\n * Data codewords for one version and level: the payload, terminated, padded, and split into blocks\n * with correction bytes appended, then interleaved back into the single stream the grid is filled\n * from.\n *\n * The interleaving is the part worth stating plainly, because it is invisible in the output and\n * silent when wrong. From version 3 the codewords are cut into several blocks that are corrected\n * independently, and they are not written into the grid one block after another — a byte is taken\n * from each block in turn. That is the whole reason a code survives a coffee ring: physical damage\n * lands on one region of the grid, and interleaving spreads that region across every block so no\n * single block takes more errors than it can correct. Written block-by-block instead, the code still\n * scans perfectly while it is clean, and stops scanning the first time anything touches it.\n */\nfunction assembleCodewords(\n  bits: number[],\n  version: number,\n  ecl: ErrorCorrectionLevel\n): Uint8Array {\n  const capacity = dataCodewords(version, ecl) * 8\n  const padded = bits.slice()\n  // Terminator, then out to a byte boundary, then the two alternating pad codewords the spec names.\n  for (let i = 0; i < 4 && padded.length < capacity; i++) padded.push(0)\n  while (padded.length % 8 !== 0) padded.push(0)\n  for (let pad = 0xec; padded.length < capacity; pad ^= 0xec ^ 0x11) {\n    for (let i = 7; i >= 0; i--) padded.push((pad >>> i) & 1)\n  }\n\n  const data = new Uint8Array(padded.length / 8)\n  for (let i = 0; i < padded.length; i++) data[i >>> 3] |= padded[i] << (7 - (i & 7))\n\n  const blockCount = EC_BLOCKS[ecl][version]\n  const ecLength = EC_CODEWORDS_PER_BLOCK[ecl][version]\n  const rawCount = totalCodewords(version)\n  const shortBlocks = blockCount - (rawCount % blockCount)\n  const shortLength = Math.floor(rawCount / blockCount)\n  const generator = generatorPolynomial(ecLength)\n\n  const blocks: { data: Uint8Array; ec: Uint8Array }[] = []\n  for (let i = 0, offset = 0; i < blockCount; i++) {\n    const length = shortLength - ecLength + (i < shortBlocks ? 0 : 1)\n    const chunk = data.subarray(offset, offset + length)\n    offset += length\n    blocks.push({ data: chunk, ec: remainderBytes(chunk, generator) })\n  }\n\n  const result = new Uint8Array(rawCount)\n  let at = 0\n  const longest = shortLength - ecLength + 1\n  for (let i = 0; i < longest; i++) {\n    for (const block of blocks) if (i < block.data.length) result[at++] = block.data[i]\n  }\n  for (let i = 0; i < ecLength; i++) {\n    for (const block of blocks) result[at++] = block.ec[i]\n  }\n  return result\n}\n\n// --- drawing the grid ---------------------------------------------------------------------------------\n\nconst MASK_FUNCTIONS: readonly ((x: number, y: number) => boolean)[] = [\n  (x, y) => (x + y) % 2 === 0,\n  (_x, y) => y % 2 === 0,\n  (x) => x % 3 === 0,\n  (x, y) => (x + y) % 3 === 0,\n  (x, y) => (Math.floor(x / 3) + Math.floor(y / 2)) % 2 === 0,\n  (x, y) => ((x * y) % 2) + ((x * y) % 3) === 0,\n  (x, y) => (((x * y) % 2) + ((x * y) % 3)) % 2 === 0,\n  (x, y) => (((x + y) % 2) + ((x * y) % 3)) % 2 === 0,\n]\n\nclass Grid {\n  readonly size: number\n  readonly modules: boolean[][]\n  readonly reserved: boolean[][]\n\n  constructor(readonly version: number, readonly ecl: ErrorCorrectionLevel) {\n    this.size = version * 4 + 17\n    this.modules = Array.from({ length: this.size }, () => new Array<boolean>(this.size).fill(false))\n    this.reserved = Array.from({ length: this.size }, () => new Array<boolean>(this.size).fill(false))\n  }\n\n  private setFunction(x: number, y: number, dark: boolean): void {\n    this.modules[y][x] = dark\n    this.reserved[y][x] = true\n  }\n\n  drawFunctionPatterns(): void {\n    for (let i = 0; i < this.size; i++) {\n      this.setFunction(6, i, i % 2 === 0)\n      this.setFunction(i, 6, i % 2 === 0)\n    }\n    this.drawFinder(3, 3)\n    this.drawFinder(this.size - 4, 3)\n    this.drawFinder(3, this.size - 4)\n\n    const positions = alignmentPositions(this.version)\n    const last = positions.length - 1\n    for (let i = 0; i <= last; i++) {\n      for (let j = 0; j <= last; j++) {\n        // The three corners are already finder patterns; an alignment pattern there would erase one.\n        if ((i === 0 && j === 0) || (i === 0 && j === last) || (i === last && j === 0)) continue\n        this.drawAlignment(positions[i], positions[j])\n      }\n    }\n\n    // Reserves the format area as well as filling it; the real bits are written once a mask is chosen.\n    this.drawFormatBits(0)\n    this.drawVersionBits()\n  }\n\n  private drawFinder(cx: number, cy: number): void {\n    for (let dy = -4; dy <= 4; dy++) {\n      for (let dx = -4; dx <= 4; dx++) {\n        const x = cx + dx\n        const y = cy + dy\n        if (x < 0 || x >= this.size || y < 0 || y >= this.size) continue\n        const ring = Math.max(Math.abs(dx), Math.abs(dy))\n        this.setFunction(x, y, ring !== 2 && ring !== 4)\n      }\n    }\n  }\n\n  private drawAlignment(cx: number, cy: number): void {\n    for (let dy = -2; dy <= 2; dy++) {\n      for (let dx = -2; dx <= 2; dx++) {\n        this.setFunction(cx + dx, cy + dy, Math.max(Math.abs(dx), Math.abs(dy)) !== 1)\n      }\n    }\n  }\n\n  /**\n   * The fifteen format bits, twice.\n   *\n   * Two copies, in two corners, because these fifteen bits say which mask was applied and at which\n   * level — without them the grid cannot be unmasked and nothing else in it can be read. They carry\n   * their own BCH correction and are then XORed with a fixed pattern, which is what stops an all-zero\n   * choice (level M, mask 0) from producing a blank region that a scanner would read as background.\n   */\n  drawFormatBits(mask: number): void {\n    const data = (FORMAT_EC_BITS[this.ecl] << 3) | mask\n    let rem = data\n    for (let i = 0; i < 10; i++) rem = (rem << 1) ^ ((rem >>> 9) * 0x537)\n    const bits = (((data << 10) | rem) ^ 0x5412) & 0x7fff\n    const bit = (i: number) => ((bits >>> i) & 1) !== 0\n\n    for (let i = 0; i <= 5; i++) this.setFunction(8, i, bit(i))\n    this.setFunction(8, 7, bit(6))\n    this.setFunction(8, 8, bit(7))\n    this.setFunction(7, 8, bit(8))\n    for (let i = 9; i < 15; i++) this.setFunction(14 - i, 8, bit(i))\n\n    for (let i = 0; i < 8; i++) this.setFunction(this.size - 1 - i, 8, bit(i))\n    for (let i = 8; i < 15; i++) this.setFunction(8, this.size - 15 + i, bit(i))\n    // Always dark, in every code ever made, and a scanner uses it to orient itself.\n    this.setFunction(8, this.size - 8, true)\n  }\n\n  private drawVersionBits(): void {\n    if (this.version < 7) return\n    let rem = this.version\n    for (let i = 0; i < 12; i++) rem = (rem << 1) ^ ((rem >>> 11) * 0x1f25)\n    const bits = (this.version << 12) | rem\n    for (let i = 0; i < 18; i++) {\n      const dark = ((bits >>> i) & 1) !== 0\n      const a = this.size - 11 + (i % 3)\n      const b = Math.floor(i / 3)\n      this.setFunction(a, b, dark)\n      this.setFunction(b, a, dark)\n    }\n  }\n\n  /**\n   * Pours the codewords into everything the function patterns left over.\n   *\n   * Two modules wide, starting at the bottom right, snaking up and then down and skipping the sixth\n   * column outright because the vertical timing line lives there. Getting the turn wrong produces a\n   * grid that looks entirely plausible and decodes to nothing.\n   */\n  drawCodewords(data: Uint8Array): void {\n    let i = 0\n    for (let right = this.size - 1; right >= 1; right -= 2) {\n      if (right === 6) right = 5\n      for (let vertical = 0; vertical < this.size; vertical++) {\n        for (let j = 0; j < 2; j++) {\n          const x = right - j\n          const upward = ((right + 1) & 2) === 0\n          const y = upward ? this.size - 1 - vertical : vertical\n          if (this.reserved[y][x] || i >= data.length * 8) continue\n          this.modules[y][x] = ((data[i >>> 3] >>> (7 - (i & 7))) & 1) !== 0\n          i++\n        }\n      }\n    }\n  }\n\n  /** XORs the mask over the data modules only. Applying it twice puts the grid back. */\n  applyMask(mask: number): void {\n    const fn = MASK_FUNCTIONS[mask]\n    for (let y = 0; y < this.size; y++) {\n      for (let x = 0; x < this.size; x++) {\n        if (!this.reserved[y][x] && fn(x, y)) this.modules[y][x] = !this.modules[y][x]\n      }\n    }\n  }\n\n  /**\n   * The spec's four penalties, low is better.\n   *\n   * A mask is not decoration and not a hash of the content: it exists so that the data region does\n   * not accidentally grow long runs, large blocks, or something shaped like a finder pattern, any of\n   * which makes a scanner lose the grid. All eight are tried and the least bad is kept, which is why\n   * two codes for nearly identical text can look completely different.\n   */\n  penalty(): number {\n    const n = this.size\n    let score = 0\n\n    for (let y = 0; y < n; y++) {\n      for (const horizontal of [true, false]) {\n        let runColor = false\n        let runLength = 0\n        for (let i = 0; i < n; i++) {\n          const dark = horizontal ? this.modules[y][i] : this.modules[i][y]\n          if (dark === runColor) {\n            runLength++\n            if (runLength === 5) score += 3\n            else if (runLength > 5) score += 1\n          } else {\n            runColor = dark\n            runLength = 1\n          }\n        }\n      }\n    }\n\n    for (let y = 0; y < n - 1; y++) {\n      for (let x = 0; x < n - 1; x++) {\n        const c = this.modules[y][x]\n        if (c === this.modules[y][x + 1] && c === this.modules[y + 1][x] && c === this.modules[y + 1][x + 1]) {\n          score += 3\n        }\n      }\n    }\n\n    // 1:1:3:1:1 with four light modules on one side — the proportions of a finder pattern. A scanner\n    // that finds one of these in the data has been told the code is somewhere it is not.\n    const FINDER = [true, false, true, true, true, false, true]\n    const matches = (read: (i: number) => boolean, i: number): boolean => {\n      for (let k = 0; k < 7; k++) if (read(i + k) !== FINDER[k]) return false\n      const before = [-4, -3, -2, -1].every((d) => i + d < 0 || !read(i + d))\n      const after = [7, 8, 9, 10].every((d) => i + d >= n || !read(i + d))\n      return before || after\n    }\n    for (let line = 0; line < n; line++) {\n      const row = (i: number) => this.modules[line][i]\n      const column = (i: number) => this.modules[i][line]\n      for (let i = 0; i + 7 <= n; i++) {\n        if (matches(row, i)) score += 40\n        if (matches(column, i)) score += 40\n      }\n    }\n\n    let dark = 0\n    for (const row of this.modules) for (const cell of row) if (cell) dark++\n    const total = n * n\n    score += Math.floor(Math.abs(dark * 20 - total * 10) / total) * 10\n\n    return score\n  }\n}\n\n// --- the public encoder ---------------------------------------------------------------------------------\n\nexport interface EncodeQrOptions {\n  /** Defaults to `\"M\"`. May be raised by `boost`. */\n  errorCorrection?: ErrorCorrectionLevel\n  /**\n   * Use the strongest level that still fits the chosen version, rather than exactly the one asked\n   * for. Defaults to `true`.\n   *\n   * The grid is sized in whole versions, so a payload almost always leaves slack in the last one it\n   * fits into — and that slack is only ever spent on padding bytes otherwise. Spending it on error\n   * correction instead costs nothing: the code is the same size, drawn at the same scale, and\n   * survives more damage. Turn it off only when something downstream depends on the level being\n   * exactly what was requested.\n   */\n  boost?: boolean\n  /**\n   * Smallest version to consider, 1–40. Defaults to 1.\n   *\n   * Worth setting for a code whose payload changes while it is on screen — a rotating token, a\n   * cart total, a field someone is typing into. Left at 1, the grid changes size the moment the\n   * text crosses a capacity boundary, so the code visibly jumps and rescales mid-scan.\n   */\n  minVersion?: number\n  /** Largest version to consider, 1–40. Defaults to 40. */\n  maxVersion?: number\n  /** Force a mask 0–7 instead of scoring all eight. For tests and reproducible output. */\n  mask?: number\n}\n\n/**\n * Encodes `value` into a grid of modules. Pure, synchronous, and dependency-free, so it runs the\n * same in a browser, on a server, in a worker, or in a test.\n *\n * Throws a `RangeError` when the text cannot be made to fit `maxVersion` at the requested level —\n * the one failure that is worth being loud about, since the alternative is a truncated code that\n * scans and gives back the wrong string.\n */\nexport function encodeQr(value: string, options: EncodeQrOptions = {}): QrMatrix {\n  const {\n    errorCorrection = \"M\",\n    boost = true,\n    minVersion = MIN_QR_VERSION,\n    maxVersion = MAX_QR_VERSION,\n    mask: forcedMask,\n  } = options\n\n  if (minVersion < MIN_QR_VERSION || maxVersion > MAX_QR_VERSION || minVersion > maxVersion) {\n    throw new RangeError(`qr-code: version range ${minVersion}–${maxVersion} is outside 1–40`)\n  }\n  if (forcedMask !== undefined && (forcedMask < 0 || forcedMask > 7 || !Number.isInteger(forcedMask))) {\n    throw new RangeError(`qr-code: mask must be an integer 0–7, got ${forcedMask}`)\n  }\n\n  const mode = pickMode(value)\n  // UTF-8, with no ECI header in front of it. The spec's default byte encoding is ISO-8859-1 and\n  // declaring UTF-8 properly would be an ECI segment — which the phone cameras people actually scan\n  // with handle less reliably than they handle bare UTF-8, because bare UTF-8 is what every\n  // generator has emitted for twenty years and every reader now sniffs for.\n  const bytes = new TextEncoder().encode(value)\n  const count = unitCount(mode, value, bytes)\n\n  let version = 0\n  let bits = 0\n  for (let candidate = minVersion; candidate <= maxVersion; candidate++) {\n    const needed = 4 + countBits(mode, candidate) + payloadBits(mode, count)\n    if (needed <= dataCodewords(candidate, errorCorrection) * 8) {\n      version = candidate\n      bits = needed\n      break\n    }\n  }\n  if (version === 0) {\n    const limit = Math.floor((dataCodewords(maxVersion, errorCorrection) * 8 - 4 - countBits(mode, maxVersion)) / 8)\n    throw new RangeError(\n      `qr-code: ${count} ${mode === \"byte\" ? \"bytes\" : \"characters\"} do not fit a version ${maxVersion} code at level ${errorCorrection} (about ${limit} bytes). Lower errorCorrection, or shorten the value — a long URL usually wants a redirect.`\n    )\n  }\n\n  let level = errorCorrection\n  if (boost) {\n    for (const candidate of EC_LEVELS.slice(EC_LEVELS.indexOf(errorCorrection) + 1)) {\n      if (bits <= dataCodewords(version, candidate) * 8) level = candidate\n    }\n  }\n\n  const buffer = new BitBuffer()\n  buffer.append(MODE_INDICATOR[mode], 4)\n  buffer.append(count, countBits(mode, version))\n  writeSegment(buffer, mode, value, bytes)\n\n  const grid = new Grid(version, level)\n  grid.drawFunctionPatterns()\n  grid.drawCodewords(assembleCodewords(buffer.bits, version, level))\n\n  let mask = forcedMask ?? 0\n  if (forcedMask === undefined) {\n    let best = Infinity\n    for (let candidate = 0; candidate < 8; candidate++) {\n      grid.applyMask(candidate)\n      grid.drawFormatBits(candidate)\n      const score = grid.penalty()\n      if (score < best) {\n        best = score\n        mask = candidate\n      }\n      grid.applyMask(candidate)\n    }\n  }\n  grid.applyMask(mask)\n  grid.drawFormatBits(mask)\n\n  return { size: grid.size, version, errorCorrection: level, mask, mode, modules: grid.modules }\n}\n\n/**\n * The dark modules as one SVG path, with the quiet zone folded into the coordinates.\n *\n * One path rather than a rectangle per module — a version 40 code is 31,329 modules and roughly half\n * of them are dark, and fifteen thousand `<rect>` elements is a document that takes longer to lay out\n * than the code took to encode. Runs along each row are merged into a single subpath for the same\n * reason.\n */\nexport function qrPath(matrix: QrMatrix, margin = 4): string {\n  const parts: string[] = []\n  for (let y = 0; y < matrix.size; y++) {\n    let run = 0\n    for (let x = 0; x <= matrix.size; x++) {\n      const dark = x < matrix.size && matrix.modules[y][x]\n      if (dark) {\n        run++\n        continue\n      }\n      if (run > 0) {\n        parts.push(`M${x - run + margin} ${y + margin}h${run}v1h-${run}z`)\n        run = 0\n      }\n    }\n  }\n  return parts.join(\"\")\n}\n\n// --- payload builders for the two things people put in a code ----------------------------------------\n\nexport interface WifiPayloadOptions {\n  ssid: string\n  password?: string\n  /** Defaults to `\"WPA\"` when a password is given and `\"nopass\"` when it is not. */\n  security?: \"WPA\" | \"WEP\" | \"nopass\"\n  /** Set for a network that does not broadcast its name. */\n  hidden?: boolean\n}\n\n/**\n * The `WIFI:` string a phone camera offers to join a network from.\n *\n * Worth a function because of the escaping, which is where hand-built versions fail and fail\n * confusingly: the format separates its fields with `;` and `:`, so a password containing either —\n * and a generated one very often does — silently truncates the payload, and the person is left\n * typing in a password that the sign says is right. A value made only of hex digits is also quoted,\n * since the format allows a raw hex key and would otherwise read `1234abcd` as bytes rather than as\n * the eight characters somebody meant.\n */\nexport function wifiPayload({ ssid, password, security, hidden }: WifiPayloadOptions): string {\n  const escape = (raw: string) => {\n    const escaped = raw.replace(/([\\\\;,:\"])/g, \"\\\\$1\")\n    return /^[0-9a-fA-F]+$/.test(raw) ? `\"${escaped}\"` : escaped\n  }\n  const type = security ?? (password ? \"WPA\" : \"nopass\")\n  const fields = [`T:${type}`, `S:${escape(ssid)}`]\n  if (password && type !== \"nopass\") fields.push(`P:${escape(password)}`)\n  if (hidden) fields.push(\"H:true\")\n  return `WIFI:${fields.join(\";\")};;`\n}\n\nexport interface OtpauthUriOptions {\n  /** The shared secret, base32, no padding. */\n  secret: string\n  /** Who the code belongs to — usually an email address or username. */\n  account: string\n  /** Your product's name. Shown as the heading in the authenticator app. */\n  issuer?: string\n  type?: \"totp\" | \"hotp\"\n  algorithm?: \"SHA1\" | \"SHA256\" | \"SHA512\"\n  digits?: number\n  /** Seconds per code, `totp` only. */\n  period?: number\n  /** Starting counter, `hotp` only. */\n  counter?: number\n}\n\n/**\n * The `otpauth://` URI an authenticator app enrols from.\n *\n * Two traps, both of which produce a code that enrols cleanly and then generates numbers the server\n * rejects. The issuer has to appear twice — once as a prefix on the label and once as a query\n * parameter — because different apps read different ones, and an app that finds only the label\n * prefix will still show the account under the right heading while one that reads only the parameter\n * will not. And the label is two components joined by a colon, so each half is escaped separately:\n * escaping the whole thing turns the separator into `%3A` and the account name becomes part of the\n * issuer, while escaping neither breaks on the first address containing a `+` or a space.\n */\nexport function otpauthUri({\n  secret,\n  account,\n  issuer,\n  type = \"totp\",\n  algorithm,\n  digits,\n  period,\n  counter,\n}: OtpauthUriOptions): string {\n  const label = issuer\n    ? `${encodeURIComponent(issuer)}:${encodeURIComponent(account)}`\n    : encodeURIComponent(account)\n  // Percent-encoded by hand rather than through URLSearchParams, which serialises a query the way\n  // a form posts one and writes a space as `+`. An `otpauth://` URI is a URI, where `+` is a\n  // literal plus and only `%20` is a space — and the format requires the issuer parameter and the\n  // issuer label prefix to be *equal*, so `issuer: \"ACME Co\"` came out as `ACME%20Co` in the label\n  // and `ACME+Co` here: two different issuers to anything decoding per RFC 3986. The code still\n  // enrols, which is what makes it worth a comment — the account lands under a heading with a plus\n  // sign in it, or the app refuses the pair outright, and neither points back at the encoder.\n  const params = [`secret=${encodeURIComponent(secret)}`]\n  if (issuer) params.push(`issuer=${encodeURIComponent(issuer)}`)\n  if (algorithm) params.push(`algorithm=${algorithm}`)\n  if (digits !== undefined) params.push(`digits=${digits}`)\n  if (type === \"totp\" && period !== undefined) params.push(`period=${period}`)\n  if (type === \"hotp\" && counter !== undefined) params.push(`counter=${counter}`)\n  return `otpauth://${type}/${label}?${params.join(\"&\")}`\n}\n\n// --- the component -----------------------------------------------------------------------------------\n\n// Encoding is pure, so the result can be shared between renders without a hook — which is the point.\n// A component that reached for useMemo would need \"use client\", and this one has no state, no events\n// and no effects: left as a plain function it renders on the server and ships no JavaScript at all\n// for what is, after all, a static picture. Small and bounded, because the common case is one code on\n// screen and the worst case is a payload that changes on every keystroke.\nconst CACHE_LIMIT = 24\nconst cache = new Map<string, QrMatrix>()\n\nfunction encodeCached(value: string, options: EncodeQrOptions): QrMatrix {\n  // The text goes last, after the five option fields. Those are an enum, a boolean, two numbers and\n  // an optional number, none of which can contain the separator — so with the free-form text at the\n  // end there is no pair of different inputs that can produce the same key, which in a cache would\n  // mean handing back somebody else's code.\n  const key = `${options.errorCorrection}|${options.boost}|${options.minVersion}|${options.maxVersion}|${options.mask}|${value}`\n  const hit = cache.get(key)\n  if (hit) return hit\n  const matrix = encodeQr(value, options)\n  cache.set(key, matrix)\n  if (cache.size > CACHE_LIMIT) {\n    const oldest = cache.keys().next()\n    if (!oldest.done) cache.delete(oldest.value)\n  }\n  return matrix\n}\n\nexport interface QrCodeProps\n  extends Omit<\n      React.ComponentPropsWithoutRef<\"svg\">,\n      // `mask` is taken over for the QR mask pattern below; SVG's own `mask` presentation attribute\n      // would be a clipping reference, which is not something you want on a code anyway.\n      \"children\" | \"role\" | \"mask\"\n    >,\n    EncodeQrOptions {\n  /** The text the code carries. A URL, a `wifiPayload()`, an `otpauthUri()`, a reference number. */\n  value: string\n  /**\n   * Light modules of quiet zone around the code. Defaults to 4, which is what the spec requires.\n   *\n   * The one setting that turns a correct code into an unreadable one without changing how it looks.\n   * A scanner locates the code by finding its border, so a code butted against other content has no\n   * border to find — it renders perfectly, it looks finished, and phones simply refuse to see it.\n   * Nothing catches this except pointing a real camera at it, which is why the default is the\n   * required value and why lowering it should mean the surface around it is already light and empty.\n   */\n  margin?: number\n  /** What a screen reader announces. Defaults to `\"QR code\"`. */\n  label?: string\n  /**\n   * Put the encoded text into that announcement. Defaults to `false`.\n   *\n   * Off, because the text is usually either useless read aloud — sixty characters of URL, letter by\n   * letter — or actively wrong to speak, which is the case for the Wi-Fi password and the 2FA secret\n   * that are the two things most often in a code. Give it a purposeful `label` instead, and put the\n   * payload on the page as text somebody can select.\n   */\n  announceValue?: boolean\n  /** Light modules on a dark ground. See the note on colour below before reaching for it. */\n  invert?: boolean\n  /** Overrides the dark modules' fill, e.g. `\"fill-primary\"`. */\n  moduleClassName?: string\n  /** Overrides the ground's fill. `\"fill-transparent\"` lets the surface behind show through. */\n  backgroundClassName?: string\n  /** Rendered instead when `value` is empty or too long to encode. Defaults to nothing. */\n  fallback?: React.ReactNode\n}\n\n/**\n * A scannable QR code, drawn as SVG.\n *\n * ```tsx\n * <QrCode value=\"https://example.com/invite/8f2a\" className=\"size-40\" label=\"Invite link\" />\n *\n * <QrCode value={wifiPayload({ ssid: \"Cafe Guest\", password: \"hunter2;drop\" })} errorCorrection=\"Q\" />\n *\n * <QrCode\n *   value={otpauthUri({ issuer: \"Acme\", account: \"ada@example.com\", secret })}\n *   className=\"size-44\"\n *   label=\"Scan with your authenticator app\"\n * />\n * ```\n *\n * The encoder is here in this file — three packing modes, all four correction levels, versions 1 to\n * 40, Reed–Solomon over GF(256), and all eight masks scored — so the component has no dependencies\n * at all, not even an icon. `encodeQr` is exported for a code you want to draw yourself, rasterise on\n * a server, or put on a label.\n *\n * **It is SVG, not canvas, and that is an accessibility decision as much as a rendering one.** A\n * canvas is a bitmap: to anything that is not an eye it is a blank rectangle of the right size, and\n * it resamples badly the moment the code is scaled or printed. Vector modules stay exact at any size,\n * and the element can carry a role and a name. But naming it is not the same as making it usable —\n * a QR code is a thing you point a second device at, so for someone using a screen reader it is\n * generally not a route to anything. Put the payload on the page as well: a real link, or a code with\n * a copy button beside it. The `label` here says what the picture is; it cannot say it out loud in a\n * form anybody can act on.\n *\n * **The colours are functional, so they do not follow the theme by default.** Dark modules on a\n * light ground is what the format specifies and what decoders assume; inverted codes are read by\n * some phones, by fewer cheap scanners, and by no printer. So the ground is white and the modules\n * are black in both themes — which is what a boarding pass, a wallet app and a bank statement all do\n * in dark mode, and it reads as deliberate rather than broken. `moduleClassName` and\n * `backgroundClassName` are there for a themed code that you have tested with the readers your users\n * actually have; keep the contrast high and the modules the darker of the two.\n */\nexport function QrCode({\n  value,\n  errorCorrection = \"M\",\n  boost = true,\n  minVersion = MIN_QR_VERSION,\n  maxVersion = MAX_QR_VERSION,\n  mask,\n  margin = 4,\n  label = \"QR code\",\n  announceValue = false,\n  invert = false,\n  moduleClassName,\n  backgroundClassName,\n  fallback = null,\n  className,\n  ...props\n}: QrCodeProps) {\n  let matrix: QrMatrix | null = null\n  if (value) {\n    try {\n      matrix = encodeCached(value, { errorCorrection, boost, minVersion, maxVersion, mask })\n    } catch {\n      // Too long for maxVersion at this level. Rendering the fallback keeps a layout from collapsing\n      // around a thrown error; call encodeQr directly if you would rather handle it yourself.\n      matrix = null\n    }\n  }\n  if (!matrix) return <>{fallback}</>\n\n  const dimension = matrix.size + margin * 2\n\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox={`0 0 ${dimension} ${dimension}`}\n      width={dimension}\n      height={dimension}\n      // Integer coordinates on a grid of squares: without this the browser antialiases every module\n      // edge, and at small sizes the seams between adjacent rows show as pale lines through the code.\n      shapeRendering=\"crispEdges\"\n      role=\"img\"\n      aria-label={announceValue ? `${label}: ${value}` : label}\n      className={cn(\"max-w-full\", className)}\n      {...props}\n    >\n      {/* Drawn rather than left to the page, because the quiet zone has to be light for the code to\n          be found at all — and a transparent one takes whatever colour the surface behind it is. */}\n      <rect\n        width={dimension}\n        height={dimension}\n        className={backgroundClassName ?? (invert ? \"fill-black\" : \"fill-white\")}\n      />\n      <path\n        d={qrPath(matrix, margin)}\n        className={moduleClassName ?? (invert ? \"fill-white\" : \"fill-black\")}\n      />\n    </svg>\n  )\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui",
  "docs": "Install any pulld component by name: add \"@pulld\": \"https://pulld.pages.dev/r/{name}.json\" to the registries block in components.json, then `npx shadcn add @pulld/<name>`. All 140 components: https://pulld.pages.dev/?utm_source=cli"
}
