QR Code

A scannable code for a link, guest Wi-Fi, a contact card, an event, an email, a phone number or text.

Pattern

≈ 1,597 tokens per use (tool 1,223 · guidance 348 · typical payload 26)How token costs are estimated

Example: QR Code

Make a QR code for our menu

Tasca Velha menu

Link

When to use

  • The user wants something to scan with another device or to print: a menu or sign-up link, guest Wi-Fi, a contact card for a badge, an event to add to calendars.
  • The host's payment tool returned a payment request link and the user wants it as a code.

When not to use

  • The user only needs the link on the device they're using. Send it as a link.
  • The agent doesn't have the link, password, number or payment link from the user or a tool result. Ask; never invent them.
  • The content is long (more than a few hundred characters). Share a file or a link instead.
  • The content is a secret beyond guest Wi-Fi: account passwords, API keys, recovery codes.

How the agent should use it

  • Pick the most specific type: wifi, contact, event, email or phone rather than text, so the phone's camera offers the right action (join, save contact, add to calendar).
  • Pass links, Wi-Fi credentials, phone numbers and email addresses exactly as given. They are data-bound: they come from the user or a tool result, never from model free text. A payment request is only ever a url the host's payment tool returned; the agent never builds or guesses one.
  • Give event date-times with their UTC offset (2026-11-05T18:00:00+01:00); use a plain date for an all-day event.
  • Leave errorCorrection at M. Use Q or H for printed codes that may get scuffed, L when the content is long.
  • Add a caption only when the title doesn't already say what scanning does, in a few words, e.g. "Scan to join". It's one quiet line under the code.

What gets encoded

The component builds the standard payload for each type, with the escaping each format needs:

typeEncodesExample
urlThe link as given (http and https only)https://menu.tascavelha.example
textThe text, up to 900 characters
wifiWIFI: string; \ ; , : " escapedWIFI:T:WPA;S:Acme Guest;P:…;;
contactvCard 3.0, CRLF line endingsBEGIN:VCARD … END:VCARD
eventiCalendar VEVENT, times in UTCDTSTART:20261105T170000Z
emailmailto: with an encoded subject and bodymailto:hello@acme.example?subject=…
phonetel: with digits and a leading +tel:+15550100

Text is encoded as UTF-8 in byte mode, so accents, scripts and emoji scan correctly. The helpers live in qr-code-utils.ts (buildPayload, wifiPayload, vCardPayload, veventPayload, encodeQr, matrixToPath, …), pure functions you can reuse on the server.

Scanning and accessibility

  • Always black on white, in dark mode too: scanners need the contrast. The quiet zone (4 modules) is part of the white tile.
  • The code is one crisp SVG path, with role="img" and a label such as "QR code for Wi-Fi network Acme Guest".
  • A readable summary sits under the code for people who can't scan: the link with an Open button, the Wi-Fi network and security with the password masked (a Show toggle and a Copy button), the contact's details, the event's local date and time. The content type (Link, Wi-Fi, …) is read to screen readers only.
  • Nothing is encoded while the tool call streams, so the code never changes under a scanner. A skeleton holds its place.
  • If the content doesn't fit at the chosen level, the component says so instead of drawing a code: "Too much data for a QR code at level H; shorten the text or use level L."

Actions

The footer has a closed set of actions: Download PNG (1024 px, quiet zone included, saved as qr-{title}.png), SVG (a vector download, named "Download SVG" for screen readers), and Copy (the encoded text, or the link for url). Each reports to onResult for analytics; nothing locks.

Installation

npx shadcn@latest add https://your-site.com/r/qr-code.json

Usage

The footer actions don't change what the agent does, so the tool can return immediately:

app/api/chat/route.ts
import { tool } from "ai"
import { qrCodeTool } from "@/components/genui/qr-code.schema"

const tools = {
  [qrCodeTool.name]: tool({
    description: qrCodeTool.description,
    inputSchema: qrCodeTool.inputSchema,
    execute: async () => ({ shown: true }),
  }),
}

Render it through the Renderer under show_qr_code, or directly. onResult reports which action the user took:

<QrCode
  {...part.input}
  status={part.state === "input-streaming" ? "streaming" : "ready"}
  onResult={(r) => track("qr_code_action", r)} // { action: "download-png" | "download-svg" | "copy" }
/>

The QR code never locks, so there's no finished state to restore from history.

With LangGraph

Push it from a graph node with ui.push({ name: "show_qr_code", props }, { message }), and render the thread's UI messages with the Renderer.

Schema

PropTypeNotes
titlestring?Defaults to the network, name, event or site
contentQrContentOne of the types below
errorCorrection"L" | "M" | "Q" | "H"Default M
captionstring?One line under the code
size"sm" | "md" | "lg"160, 220 or 280 px. Default md
content.typeFields
urlurl: http(s) only. Data-bound
texttext: up to 900 characters
wifissid, password?, security (WPA, WEP, nopass), hidden?. Password required unless nopass. Data-bound
contactname, phone?, email?, org?, title?, url?. Data-bound
eventtitle, start, end?, location?. ISO 8601 with offset, or plain dates for all-day (end inclusive)
emailto, subject?, body?. Data-bound
phonenumber. Data-bound

Host props: status (streaming or ready), onResult and className.

Source

On this page