QRCode
Renders any text or URL as a scannable QR code. The output is a self-contained SVG with no runtime dependency, whether you encode a URL, a short text string, or a 2FA enrolment URI.
Playground
<QRCode
frame="card"
value="https://ui.urbicon.de"
/>01 Examples
Basic — encode a URL
<QRCode value="https://ui.urbicon.de" frame="card" />Error-correction levels
{#each ['L', 'M', 'Q', 'H'] as level}
<QRCode value="https://ui.urbicon.de" errorCorrection={level} size={120} frame="card" />
{/each}Sizes
{#each [120, 160, 220] as px}
<QRCode value="https://ui.urbicon.de" size={px} frame="card" />
{/each}Custom colours
<QRCode
value="https://ui.urbicon.de"
foreground="#1e3a5f"
background="#f8fafc"
frame="card"
/>02 2FA & auth
Pass a TOTP enrolment URI straight to <QRCode> and an authenticator app
(Google Authenticator, 1Password, …) scans it to register the shared secret. This replaces an
external QR library in TwoFactorManager's qr snippet.
Use errorCorrection="H" here: the extra recovery data keeps the code readable
when it is scanned at an angle or across a second device. Never echo the secret into aria-label: the visible code already contains it, and the label is read aloud and
exposed in the accessibility tree.
Encode an otpauth:// enrolment URI
<QRCode
value="otpauth://totp/Urbicon:alice@example.com?secret=JBSWY3DPEHPK3PXP&issuer=Urbicon&period=30"
errorCorrection="H"
size={200}
frame="card"
/>03 Encoding capacity & errors
The smallest QR version (1–40) that fits your data is chosen automatically, along with the most efficient mode for it (numeric, alphanumeric, or UTF-8 byte). You do not pick a version yourself.
Bound the size with maxVersion when a code has to stay physically small. Data
that overflows that bound calls onError and renders a visible fallback in place
of the code, so the surrounding page keeps rendering. Type past the maxVersion=4 capacity below to see the fallback.
Live capacity demo
<script>
let payload = $state('https://ui.urbicon.de/docs');
</script>
<Textarea label="Payload" bind:value={payload} />
<QRCode value={payload} maxVersion={4} frame="card" onError={(e) => console.warn(e.message)} />04 Accessibility
One named image
Renders as role="img" with an aria-label that defaults to a localized
"QR code" and is overridable per instance.
Never echo a sensitive payload
Never echo sensitive payloads (a 2FA secret, a signed token) into aria-label: it is announced aloud and exposed in the accessibility tree.
Contrast and scannability
For guaranteed scannability keep high-contrast dark-on-light modules. frame="card" supplies the light ground; the default foreground is currentColor, so an unframed code inherits the surrounding text colour.
The failure state stays labelled
When encoding fails, the visible fallback also carries role="img" with the same label,
so assistive tech is never left with an empty region.
05 API Reference
16 propsProp | Type | Default | Description | |
|---|---|---|---|---|
value required | string | — | The data to encode — text, a URL, an otpauth:// URI, etc. | |
aria-label | string | — | Accessible name announced for the code. Defaults to a localized "QR code". Avoid echoing sensitive payloads (e.g. a 2FA secret) into this label. | |
background | string | 'transparent' | Background fill — any CSS colour. | |
class | string | — | Extra classes merged onto the root wrapper. | |
errorCorrection | LMQH | 'M' | Error-correction level: higher levels survive more damage/occlusion at the cost of a denser (larger) code. L≈7%, M≈15%, Q≈25%, H≈30%. | |
foreground | string | 'currentColor' | Colour of the dark modules — any CSS colour. Defaults to currentColor so
the code inherits the surrounding text colour. For reliable scanning keep a
high-contrast dark-on-light pairing (see frame="card"). | |
id | string | — | Root id. | |
maxVersion | number | — | Upper bound on the QR version (1–40). Data that does not fit calls onError and renders the fallback instead of encoding. | |
minVersion | number | — | Lower bound on the QR version (1–40) to force a minimum size. | |
onError | (error: Error) => void | — | Called when the data cannot be encoded (e.g. too long for maxVersion). | |
preset | string | — | Apply a named preset registered via <BlocksProvider presets={{ QRCode: {...} }}>. | |
quietZone | number | 4 | Width of the mandatory light border, in modules. The spec requires ≥4. | |
size | number | 160 | Rendered edge length in pixels (the code is square). | |
slotClasses | Partial<Record<QRCodeSlots, string>> | — | Per-slot class overrides merged with tv() styles. Slots: root (what class
also targets) | svg | fallback. | |
unstyled | boolean | — | Remove all default tv() classes — only user-provided classes apply. | |
...QRCodeVariants variant | VariantProps | — | Styling variants from QRCodeVariants |
06 Types
Local type definitions used by this component.
Name | Kind | Category | Used by | Description | |
|---|---|---|---|---|---|
QRCodeProps | interface | props | 0 | — | |
QRCodeSlots | type | variant | 0 | Slot names derived from the tv() config above — single source of truth for slotClasses. | |
QRCodeVariants | type | variant | 1 | — | |
SlotNames | type | helper | 0 | Extracts the slot-name union from a slotted tv() config function — the
companion to VariantProps. The slot-mode overload returns
(props?) => { [K in keyof S]: SlotFn }, so keyof ReturnType<T> is exactly
the set of slot names a component declares in tv({ slots: … }).
Use it to type a component's slotClasses prop from the single source of
truth (its *.variants.ts) instead of hand-maintaining a parallel union
that silently drifts when a slot is added or renamed: | |
VariantProps | type | helper | 1 | — |
07 Installation
Import
import { QRCode } from '@urbicon-ui/blocks';