QR code
The custom element is <hui-qr-code>.
Overview
Encodes its value as a QR code and draws it as one SVG path, dark modules on a white tile whatever the theme. A phone camera reads dark on light far more reliably than the reverse, so the code is deliberately not themed.
Example
Source
<hui-qr-code value="https://ledger.example.com/link/4f2a9c17e5" label="Scan to link your mobile device"></hui-qr-code>The Go template that renders it:
<hui-qr-code value="{{ .PairingURL }}" label="Scan to link your mobile device"></hui-qr-code>API
Attributes4
| Name | Type | Default | Description |
|---|---|---|---|
value | string | "" | What the code encodes: a URL, a pairing token. Reflected. A new value redraws the code, so an HTMX swap sending a fresh token is the whole update. |
label | string | "QR code" | The accessible name. The value itself is never announced - it is usually a secret or an opaque token - so put the instruction in text beside the code. |
level | "L" | "M" | "Q" | "H" | "M" | Error correction. An unrecognised value falls back to M rather than failing to draw. |
size | number | 160 | The drawn size of the code in pixels, excluding the tile's 16px padding. |
Properties4
| Name | Type | Default | Description |
|---|---|---|---|
value | string | '' | Reflects to the value attribute. |
label | string | 'QR code' | Reflects to the label attribute. |
level | QrLevel | 'M' | Reflects to the level attribute, normalised. |
size | number | 160 | Reflects to the size attribute. |
::part() hooks2
| Name | Description |
|---|---|
tile | The white tile: the border, the xl radius and the 16px padding that is the code's quiet zone. It carries role="img" and the label. |
code | The <svg> itself, aria-hidden, holding one path of unit squares. |
States
Error correction and size
Higher correction survives more damage to the printed code at the cost of more modules, so the same value needs a denser grid. size is the drawn width of the code itself, not counting the tile's padding.
Source
<div style="display:flex;gap:1rem;align-items:flex-start;flex-wrap:wrap"> <hui-qr-code value="https://ledger.example.com/link/4f2a9c17e5" level="L" size="120" label="Level L"></hui-qr-code> <hui-qr-code value="https://ledger.example.com/link/4f2a9c17e5" level="H" size="120" label="Level H"></hui-qr-code></div>Nothing to encode
An empty value draws the tile and no code, sets data-invalid and warns once - never repeatedly, however often it redraws. A value too long for any version at the chosen level does the same.
Source
<hui-qr-code value="" label="No pairing code yet"></hui-qr-code>Server-side mechanics
The server owns the value. The element encodes what it is handed and draws it; it never fetches, generates or stores a token, and it keeps no copy of the value beyond the attribute. Re-rendering the same markup draws the same code, so an hx-swap that replaces the element with a fresh pairing token is the entire update. A Go host that would rather not ship an encoder to the browser can render the same code server-side as SVG and send that instead - this element is for hosts that would rather send the value.
Accessibility
- The tile is
role="img"named bylabel; the SVG isaria-hidden, so a screen reader is told a code is present and not read a meaningless path. - The label defaults to "QR code" rather than being empty, so an unnamed element is never an unlabelled image.
- Under forced colours the tile and the modules keep their own colours through
forced-color-adjust: none, because an inverted code does not scan.
Gotchas
- The tile is white and the modules dark in the dark theme and under forced colours alike. This is not an oversight: a camera reads dark on light far more reliably than the reverse, and a themed code is a code that sometimes does not scan.
- The tile's 16px padding is the quiet zone a scanner needs. Removing it with
::part(tile)will produce a code that looks right and reads unreliably. - The value is not in the accessibility tree. A reader who cannot see the code needs the alternative - a link, a copyable code - in the markup beside it, as the example does.