Show QR codes in your Bubble Tea
app, or generate them as images. ntqrcode uses Kitty graphics where
available and Unicode blocks everywhere else.
go-qr does the QR generation. This library adds a Bubble Tea component that fits codes to the available space, handles resizing and graphics cleanup, and switches between Kitty images and Unicode blocks. It also provides a small encoding API with input and image-size limits, Unicode handling, and a quiet zone around every code.
Use go-qr directly for general QR generation, image export, or decoding. Use
ntqrcode when you want to display QR codes inside a Bubble Tea app.
Built for gloss, it's a small, experimental companion to NTCharts.
┌─────────────────────────────────────────┐
│ │
│ │
│ █▀▀▀▀▀█ ▄▀ █▄▄▀█▀▀▄ ▄▀▄ █▀▀▀▀▀█ │
│ █ ███ █ █▄ ▄█▀▄ █▀▀▀▄▀█▄▄ █ ███ █ │
│ █ ▀▀▀ █ █ ▀▀ ▀ █▄█▀ ▄█▄▀ █ ▀▀▀ █ │
│ ▀▀▀▀▀▀▀ █ ▀ █ █▄▀ █ ▀▄█▄█ ▀▀▀▀▀▀▀ │
│ █ ███▀▀▄ ▄█ ▄▀▀▀ ▀▄▄█▄▀▄▀ ██▀██▄▄ │
│ █▄ █ ▀ ▀▀██ ▄▀ ▀ ▄█▀▄█▀ █▄█▀▄ │
│ █ ▄█ ▀▄▄███▄▄▄█▄ ▀ ▄ ▀▄▀█▄ ▀█▄██ │
│ ▀▀▄▀▀▄▀▀█▀▀▄ ▄▄ ▄▀ █▄██▄████▀▄█▀ │
│ ▄█▀██▄▀▀ ▄█▀█▀▀ ▄█▄██▄▀▄▀▀▄█▀▄▄ █ │
│ ▄▄█▀█▀ ▄▀ █▀▄▀███▄ ███ ▄▄██▄██▄ │
│ ▄▀ ▀ ▀ ▀ ▄▄ ▀ █▀▄▄ ▀▄ █ ▄ ▀▀▄▀█ │
│ █ ▀ █▀██▄▄ ▄███▀ ▀ ▀█▄ ▄▀█▄ █▄▀ │
│ ▀ ▀▀▄▀▄ ███▀█▀ ▄▄ █ █▀▀▀█ ▄ ▄ │
│ █▀▀▀▀▀█ ▄▄█▀█▄▄█ ▀▄▀▄███ ▀ █▄█▄▄ │
│ █ ███ █ █▄▀█▄▀▄▄▄ ▀▀▄█ ▄██▀▀█▀ ██ │
│ █ ▀▀▀ █ ▀▄▄ ▄ ▄ ▄ ▄█▄▄ ▄ ▄█▀▄▄█ │
│ ▀▀▀▀▀▀▀ ▀ ▀▀ ▀ ▀ ▀ ▀▀▀▀ ▀ ▀ │
│ │
│ │
└─────────────────────────────────────────┘
Requires Go 1.26.8+ and Bubble Tea v2. The API is experimental.
Development builds prefer Go 1.27.2 or newer; Go selects it automatically
with the default GOTOOLCHAIN=auto setting.
As of v0.2.0, the module is nimbleterminal.dev/ntqrcode. When upgrading
from v0.1.x, replace github.com/NimbleMarkets/ntcharts-qrcode/qrcode imports
with nimbleterminal.dev/ntqrcode/qrcode; the public API is unchanged.
go get nimbleterminal.dev/ntqrcode/qrcode
go run ./examples/qrcodeThe example displays two QR codes. Press n to change the first URL, 2 to
hide or restore the second code, g to switch rendering modes, and q to quit.
If your terminal is too narrow for both codes, press 2 to make room.
import "nimbleterminal.dev/ntqrcode/qrcode"
code, err := qrcode.Encode("https://nimble.markets/", qrcode.Options{})
if err != nil {
return err
}
matrix := code.Matrix() // copy; true = black, includes the white quiet zone
img, err := code.Image(8) // opaque image.Image, exactly 8 pixels per module
// Handle err, then png.Encode(writer, img) to export it.Options{} uses medium error correction, with an automatic boost when a
stronger level fits in the same size. You can also request Low, Quartile,
or High.
Set MinVersion and MaxVersion to limit the QR size (versions 1–40). Set
them to the same value to keep the size fixed as content changes. Text that
won't fit returns ErrCapacity. Unicode text is supported, including mixed
Japanese and Latin text.
qr, err := qrcode.New(code, qrcode.Config{NextID: hostNextImageID})
// Handle err, retain qr, and run the commands returned by these methods:
cmd := tea.Batch(
qr.SetSize(columns, rows),
qr.SetTerminal(useKitty, cellPixelWidth, cellPixelHeight),
qr.Update(msg),
)
// Render qr.View() as a whole; inspect qr.Err() for fit/layout failures.
// Before removing it, execute the command returned by qr.Close().Execute every returned command. See the complete host example for batching, native/WASM startup, and cleanup before quitting.
- Use an existing
picture.Model.Init()/Update()for asynchronous terminal detection. Forward itsCellPixelSize()and the user's graphics preference throughSetTerminal. Unknown or unsupported Kitty capability uses glyphs. - Broadcast messages to all components. Each filters its own image frames.
NextIDmust allocate fresh positive 24-bit IDs shared with every picture in the application, including across replacement and resize. Do not reuse IDs.- Execute commands from setters,
Update, andClose. A model is a pointer with event-loop-owned state; do not copy it or mutate it concurrently. SetCode(nil)clears content. Encode replacements before setting them so invalid input can leave the old content intact.Closeis idempotent and final.errors.Is(qr.Err(), qrcode.ErrDoesNotFit)identifies insufficient space. The component never truncates the matrix or removes the quiet zone.
View() returns a string you can include in your own tea.View. Add your
own keyboard bindings, borders, and labels around it.
Images use black modules on an opaque white background, with a four-module
quiet zone. They scale in whole pixels to keep the edges sharp. If a code
won't fit in the terminal, the component reports ErrDoesNotFit rather than
cutting it off.
Glyph rendering uses colored half-blocks and adapts to the terminal's cell
size. It starts with an 8×16-pixel estimate; pass measured dimensions through
SetTerminal when you have them.
Apple Terminal draws half-blocks with gaps that can break a QR code.
Use Config{SolidCells: true} to fill whole cells instead. This takes four
times the area—about 66×33 cells for a short URL. The example enables it when
TERM_PROGRAM=Apple_Terminal; you can also set NTCHARTS_QRCODE_SOLID=1,
including inside tmux.
Input must be nonempty UTF-8 and no more than 7,089 bytes. How much fits in a
code depends on the text and correction level. Image(scale) accepts integer
scales from 1 to 16, up to 2,048 pixels per edge. Terminal output is limited to
256 Kitty cells per axis or 65,536 glyph cells.
The example has been scanned with a phone in Apple Terminal (solid cells) and iTerm2 (half-blocks). Kitty graphics and tmux have also been checked visually. See DEVELOP.md for the recorded results and remaining checks.
task ci # full checks, builds, and vulnerability scans
task test # tests without a vulnerability-database query
task build-ex-qrcode # build bin/ntqrcode
task serve-wasm-site # http://localhost:8000/ntqrcode/
task clean # remove generated binaries and demo assetsThe browser demo runs the same example through go-booba.
wasm.work selects the Bubble Tea and clipboard forks needed for WASM;
native builds use upstream versions.
Encoding uses piglig/go-qr v2.6.0. Rendering uses NTCharts v2.7.2. Tests independently decode generated images, Kitty PNG data, and reconstructed glyph output. They also cover Unicode, capacity limits, resizing, and cleanup. Camera and terminal checks are recorded separately in DEVELOP.md.
Run task ci before submitting changes. It checks formatting, dependencies,
race tests, vet, Windows/native/WASM builds, and known vulnerabilities. The
vulnerability scans require network access. The Pages workflow runs these
checks before deploying; configure GitHub Pages to use GitHub Actions to
enable it. See DEVELOP.md for implementation notes and the
dependency review.
MIT License — Copyright (c) 2026 Neomantra Corp. Dependency notices are in THIRD_PARTY_NOTICES.md.
Made with ❤️ and 🔥 by the team behind Nimble.Markets.