QRCode

A string a camera can read — framed, titled, or folded away behind a button.

A string, drawn as something a camera can read. Give it a value and compose the shape around it: a bare code, a code in a bordered panel, a panel with a title over it, or a code folded away behind a button and brought up in a popover.

The encoding is ours and has no dependency behind it — the version grows to fit whatever you pass, and the whole code is drawn as a single path rather than one node per module, which for a dense code is the difference between appearing and appearing eventually.

Installation

QRCode ships with the library — no separate install.

import { QRCode, Button, Text, Avatar } from 'panelui-native';

Or copy the source into your project, to own and edit it:

npx panelui-cli@latest add qr-code

Usage

<QRCode value="https://panelui.dev">
  <QRCode.Frame>
    <QRCode.Header>
      <QRCode.Title>Documentation</QRCode.Title>
    </QRCode.Header>
    <QRCode.Panel>
      <QRCode.Canvas />
    </QRCode.Panel>
  </QRCode.Frame>
  <QRCode.Caption>Scan to open the docs</QRCode.Caption>
</QRCode>

Composition

<QRCode value="…" presentation="popover">
  <QRCode.Trigger>                      {/* …or no Trigger/Content at all, */}
    <Button>Show the code</Button>      {/* and the parts draw where they are */}
  </QRCode.Trigger>
  <QRCode.Content>
    <QRCode.Frame>                      {/* the tray */}
      <QRCode.Header>                   {/* the strip across the top of it */}
        <QRCode.Title>Pair a device</QRCode.Title>
        <QRCode.Action>Expires in 5m</QRCode.Action>
      </QRCode.Header>
      <QRCode.Panel>                    {/* the card the code is drawn on */}
        <QRCode.Canvas />               {/* the code itself */}
        <QRCode.Logo>…</QRCode.Logo>    {/* clears a square in the middle */}
      </QRCode.Panel>
    </QRCode.Frame>
    <QRCode.Caption>Scan to open the docs</QRCode.Caption>
    <QRCode.Value />                    {/* the string, for anyone who cannot scan */}
  </QRCode.Content>
</QRCode>

The order and the presence of the parts are yours — a bare <QRCode.Canvas /> with nothing around it is a perfectly good QR code, and everything else is optional shape.

QRCode.Content re-provides the context inside itself. Portal content mounts under the portal host rather than under the element that opened it, so a provider outside the popover is not an ancestor of what is in it.

Examples

In a frame

The tray, its title strip, and a line under the whole thing.

<QRCode value="https://panelui.dev" size="lg">
  <QRCode.Frame>
    <QRCode.Header>
      <QRCode.Title>Documentation</QRCode.Title>
      <QRCode.Action>panelui.dev</QRCode.Action>
    </QRCode.Header>
    <QRCode.Panel>
      <QRCode.Canvas />
    </QRCode.Panel>
  </QRCode.Frame>
  <QRCode.Caption>Scan to open the docs</QRCode.Caption>
</QRCode>

With something in the header

The trailing slot takes whatever the title needs qualifying with — here, how long the code is good for.

<QRCode value="https://panelui.dev/pair/8f2a41" size="lg">
  <QRCode.Frame>
    <QRCode.Header>
      <QRCode.Title>Pair a device</QRCode.Title>
      <QRCode.Action>Expires in 5m</QRCode.Action>
    </QRCode.Header>
    <QRCode.Panel>
      <QRCode.Canvas />
    </QRCode.Panel>
  </QRCode.Frame>
  <QRCode.Value />
</QRCode>

Folded away behind a button

The same code, brought up over the page when it is wanted and not before.

<QRCode value="https://panelui.dev" presentation="popover">
  <QRCode.Trigger>
    <Button variant="outline">Show QR code</Button>
  </QRCode.Trigger>
  <QRCode.Content>
    <QRCode.Canvas />
    <QRCode.Caption>Scan to open the docs</QRCode.Caption>
  </QRCode.Content>
</QRCode>

Up from the bottom edge

The same thing in a sheet, which is the shape a phone wants for something you hold up to another screen.

<QRCode value="https://panelui.dev" presentation="bottom-sheet" size="lg">
  <QRCode.Trigger>
    <Button>Share</Button>
  </QRCode.Trigger>
  <QRCode.Content>
    <QRCode.Canvas />
    <QRCode.Caption>Anyone with this code can open the page</QRCode.Caption>
  </QRCode.Content>
</QRCode>

With a mark in the middle

A logo clears a square of modules and fills it; the error correction is raised to cover the loss.

<QRCode value="https://panelui.dev" size="lg">
  <QRCode.Frame>
    <QRCode.Header>
      <QRCode.Title>PanelUI</QRCode.Title>
    </QRCode.Header>
    <QRCode.Panel>
      <QRCode.Canvas />
      <QRCode.Logo>
        <Avatar fallback="P" size="sm" />
      </QRCode.Logo>
    </QRCode.Panel>
  </QRCode.Frame>
</QRCode>

A WiFi network

Not every code is a URL. This one joins a network when it is scanned.

<QRCode value="WIFI:T:WPA;S:PanelUI Guest;P:hunter2;;" size="lg">
  <QRCode.Frame>
    <QRCode.Header>
      <QRCode.Title>PanelUI Guest</QRCode.Title>
      <QRCode.Action>WPA2</QRCode.Action>
    </QRCode.Header>
    <QRCode.Panel>
      <QRCode.Canvas />
    </QRCode.Panel>
  </QRCode.Frame>
  <QRCode.Caption>Scan to join the network</QRCode.Caption>
</QRCode>

Bare

No tray, no caption — the code and nothing else.

<QRCode value="https://panelui.dev" size="sm">
  <QRCode.Canvas />
</QRCode>

Versions

Framed

The tray, the title strip, and the code on the card inside it.

<QRCode value="https://panelui.dev" size="lg">
  <QRCode.Frame>
    <QRCode.Header>
      <QRCode.Title>Documentation</QRCode.Title>
      <QRCode.Action>panelui.dev</QRCode.Action>
    </QRCode.Header>
    <QRCode.Panel>
      <QRCode.Canvas />
    </QRCode.Panel>
  </QRCode.Frame>
  <QRCode.Caption>Scan to open the docs</QRCode.Caption>
</QRCode>

With a header

A trailing slot in the header, and the encoded string underneath.

<QRCode value="https://panelui.dev/pair/8f2a41" size="lg">
  <QRCode.Frame>
    <QRCode.Header>
      <QRCode.Title>Pair a device</QRCode.Title>
      <QRCode.Action>Expires in 5m</QRCode.Action>
    </QRCode.Header>
    <QRCode.Panel>
      <QRCode.Canvas />
    </QRCode.Panel>
  </QRCode.Frame>
  <QRCode.Value />
</QRCode>

In a popover

Folded away behind a row until it is wanted, the way a colour picker folds away.

<QRCode value="https://panelui.dev" presentation="popover">
  <QRCode.Trigger>
    <Button variant="outline">Show QR code</Button>
  </QRCode.Trigger>
  <QRCode.Content>
    <QRCode.Canvas />
    <QRCode.Caption>Scan to open the docs</QRCode.Caption>
  </QRCode.Content>
</QRCode>

Variants

size

  • sm
  • md (default)
  • lg
<QRCode value="https://panelui.dev" size="sm">
  <QRCode.Canvas />
</QRCode>
<QRCode value="https://panelui.dev" size="md">
  <QRCode.Canvas />
</QRCode>
<QRCode value="https://panelui.dev" size="lg">
  <QRCode.Canvas />
</QRCode>

API Reference

QRCode

PropTypeDefaultDescription
classNamestring
valuestringWhat the code encodes. Anything: a URL, a WiFi string, a vCard. Encoded as UTF-8, and the version grows to fit it.
errorCorrectionErrorCorrectionLevel'M'How much of the code can be lost and still read — L about 7%, M 15%, Q 25%, H 30%. More correction means a denser code at the same size, so M is the default. Raised automatically when a QRCode.Logo needs it.
versionnumberFix the QR version, 1–40, instead of taking the smallest that fits. Worth setting when the content changes and the code should not visibly change density with it.
presentationQRCodePresentation'inline'Where the code appears. inline draws it where it sits; the other two put it behind a QRCode.Trigger and draw it in a QRCode.Content.
openbooleanControlled open state. Ignored while presentation is inline.
onOpenChange(open: boolean) => void

QRCode.Canvas

PropTypeDefaultDescription
classNamestring
pixelSizenumberSide length in points. Defaults to the size variant's.
colorstringDark modules. See the note below before overriding this.
backgroundColorstringThe plate the modules sit on. See the note below.

QRCode.Frame

PropTypeDefaultDescription
classNamestring

QRCode.Header

PropTypeDefaultDescription
classNamestring

QRCode.Title

PropTypeDefaultDescription
classNamestring

QRCode.Action

PropTypeDefaultDescription
classNamestring

QRCode.Panel

PropTypeDefaultDescription
classNamestring

QRCode.Description

PropTypeDefaultDescription
classNamestring

QRCode.Caption

PropTypeDefaultDescription
classNamestring

QRCode.Value

PropTypeDefaultDescription
classNamestring
fullbooleanShow the whole string rather than one line of it.
PropTypeDefaultDescription
classNamestring

Every part also accepts the underlying React Native props (ViewProps or TextProps) and a className for Tailwind utilities.

Notes

The encoding is byte mode, so value can be anything — a URL, a WiFi string, a vCard, Japanese. It is encoded as UTF-8, which every reader in circulation understands.

The QR version — the module count, 21×21 up to 177×177 — is chosen as the smallest that fits what you passed. That means a code visibly gets denser as its content grows, which is usually what you want and occasionally is not: pass version to pin it, and the component will tell you at runtime if the content stops fitting.

Error correction, and what it is for

errorCorrection decides how much of the code can be lost and still read: L about 7%, M 15% (the default), Q 25%, H 30%. The correction is not free — the redundancy takes modules, so a higher level is a denser code at the same physical size, and a denser code is one a camera has to get closer to.

M is the default because a code on a screen is not being damaged by anything. Raise it when the code will be printed, put on something curved, or covered in the middle — and a QRCode.Logo raises it for you if the level you asked for could not afford the square it clears.

Making it scannable

Three things decide whether a code reads, and none of them is the code:

  • The quiet zone. Four modules of clear space all round, which QRCode.Canvas draws and paints. Do not clip it, and do not put the code flush against a border.
  • Contrast, the right way round. Dark modules on a light field, and QRCode.Canvas draws that way whatever the theme is doing. It is the one thing in the library that ignores the tokens, and it is deliberate: inverted, a code is rejected outright by a good share of scanners and found late by most of the rest, which turns a dark theme into a bug report about a code that "sometimes does not work". On a light theme the plate is white on a near-white card and reads as nothing at all, which is the intended outcome. color and backgroundColor override both.
  • Size. A code needs roughly one point per module at arm's length. sm is 128 points, which is fine for a version 2 code and marginal for a version 10 one; lg is 240.

Accessibility

The canvas has an image role and a label naming what it encodes, so a screen reader announces the destination rather than "image". QRCode.Value puts the same string on screen as selectable text, which is the way through for anyone whose camera is the thing being set up.

On this page