etiket
v0.12.0
Published
Zero-dependency barcode & QR code generator — SVG & PNG output. 40+ formats, styled QR codes, tree-shakeable. Pure TypeScript, works everywhere.
Downloads
6,875
Maintainers
Readme
[!IMPORTANT] Verified formats (18): QR Code, Data Matrix, PDF417, Aztec, Micro QR, rMQR, MaxiCode, MicroPDF417, Code 128, EAN-13, EAN-8, UPC-A, Code 39, Code 93, ITF, Codabar, GS1-128, Codablock F — verified with round-trip scan tests (zxing-wasm, rxing, gozxing) and/or 100% bit-match against Zint/bwip-js reference.
Experimental formats: DotCode, Han Xin, JAB Code — no open-source decoder exists for these formats. Encoders produce structurally valid output (Han Xin 75% reference match, finders 100%). PRs welcome.
Contributions welcome! If you find a scanning issue or want to improve an encoder, please open an issue or submit a PR. See Contributing below.
Quick Start
npm install etiketimport { barcode, qrcode } from "etiket"
const svg = barcode("Hello World")
const qr = qrcode("https://example.com", { dotType: "dots", ecLevel: "H" })CLI
npx etiket list # every supported symbology
npx etiket qr "Hello World" -o qr.svg
npx etiket qr "Hello" --terminal
npx etiket qr "Hello" --size 300 --ec H --dot-type dots
npx etiket qr "Hello" -o qr.png # .png output writes a PNG
npx etiket barcode "4006381333931" --type ean13 --show-text -o barcode.svg
npx etiket postal "SN34RD1A" --type rm4scc -o postal.svg
npx etiket datamatrix "Hello" -o dm.svg
npx etiket pdf417 "Hello" -o pdf.svg
npx etiket aztec "Hello" -o aztec.svg
npx etiket maxicode "Hello" -o maxi.svg
npx etiket wifi "MyNetwork" "secret123" -o wifi.svg
npx etiket contact "Ada Lovelace" --email [email protected] -o card.svgEvery symbology has a subcommand (qr, microqr, rmqr, barcode, postal,
datamatrix, pdf417, micropdf417, aztec, maxicode, dotcode, hanxin,
codablockf, code16k, jabcode) plus the wifi, contact and link
helpers. Add --png or use a .png output path for raster output.
Tree Shaking
Import only what you need:
import { barcode, barcodeDataURI, barcodeBase64 } from "etiket/barcode"
import { postal, encodePostal } from "etiket/postal"
import { qrcode, qrcodeDataURI, qrcodeBase64, qrcodeTerminal, microqr, rmqr } from "etiket/qr"
import { datamatrix, gs1datamatrix } from "etiket/datamatrix"
import { pdf417, micropdf417 } from "etiket/pdf417"
import { aztec } from "etiket/aztec"
import { maxicode, dotcode, hanxin, codablockf, code16k } from "etiket/2d"
import { barcodePNG, qrcodePNG, postalPNG } from "etiket/png" // PNG output
import { validateBarcode, validateQRInput } from "etiket/validators"
import { EtiketError, InvalidInputError, CapacityError } from "etiket/errors"Supported Formats
1D Barcodes
| Format | Type | Description |
| :----------------------- | :--------------------- | :----------------------------- |
| Code 128 | code128 | Auto charset (A/B/C) |
| Code 39 | code39 | 43-char set, optional check |
| Code 39 Ext | code39ext | Full ASCII |
| Code 93 | code93 | Higher density, 2 check digits |
| Code 93 Ext | code93ext | Full ASCII |
| EAN-13 | ean13 | Auto check digit |
| EAN-8 | ean8 | Auto check digit |
| EAN-5 | ean5 | Addon (book price) |
| EAN-2 | ean2 | Addon (issue number) |
| UPC-A | upca | 12-digit, auto check digit |
| UPC-E | upce | Compressed 8-digit |
| ITF | itf | Interleaved 2 of 5 |
| ITF-14 | itf14 | 14-digit with bearer bars |
| Codabar | codabar | Libraries, blood banks |
| MSI Plessey | msi | Mod10/11/1010/1110 |
| Pharmacode | pharmacode | Pharmaceutical |
| Code 11 | code11 | Telecommunications |
| GS1-128 | gs1-128 | AI parsing, FNC1, 100+ AIs |
| GS1 DataBar | gs1-databar | Omnidirectional, 14-digit GTIN |
| GS1 DataBar Limited | gs1-databar-limited | GTIN starting with 0/1 |
| GS1 DataBar Expanded | gs1-databar-expanded | Variable-length AI data |
| Identcode | identcode | Deutsche Post / DHL |
| Leitcode | leitcode | Deutsche Post routing |
| POSTNET | postnet | USPS legacy postal |
| PLANET | planet | USPS confirmation tracking |
| Plessey | plessey | UK library systems |
2D Codes
| Format | Function | Description |
| :----------------- | :---------------- | :------------------------------------------ |
| QR Code | qrcode() | Versions 1-40, all EC levels, all modes |
| Micro QR | microqr() | M1-M4 (11x11 to 17x17) |
| Data Matrix | datamatrix() | ECC 200, ASCII/C40/Text auto encoding |
| GS1 DataMatrix | gs1datamatrix() | FNC1 + AI parsing |
| PDF417 | pdf417() | Text/Byte/Numeric, 9 EC levels, ISO-8859-15 |
| MicroPDF417 | micropdf417() | Compact PDF417 for small items |
| Aztec | aztec() | Compact + full-range, no quiet zone |
| MaxiCode | maxicode() | 33×30 hexagonal, UPS shipping labels |
| rMQR | rmqr() | Rectangular Micro QR (R7x43 to R17x139) |
| Codablock F | codablockf() | Stacked Code 128 |
| Code 16K | code16k() | Stacked barcode, 2-16 rows |
| DotCode | dotcode() | Checkerboard dots, high-speed printing |
| Han Xin | hanxin() | Chinese market, 84 versions, 4 finders |
| JAB Code | jabcode() | Polychrome (4/8 color), ISO/IEC 23634 |
Each also has a raw encoder (encodeMicroQR, encodeMaxiCode, …) and, except
for JAB Code, a *PNG() variant.
Postal Barcodes
Postal symbologies are height-modulated — the data lives in each bar's vertical extent, not its width — so they have their own encoder and renderer.
| Format | type | Description |
| :----------------- | :-------- | :---------------------------- |
| POSTNET | postnet | USPS ZIP (5, 9 or 11 digits) |
| PLANET | planet | USPS PLANET (11 or 13 digits) |
| RM4SCC | rm4scc | Royal Mail (UK) |
| KIX | kix | PostNL (Netherlands) |
| Australia Post | auspost | Australia Post |
| Japan Post | jppost | Japan Post (Kasutama) |
| USPS IMb | imb | Intelligent Mail (US) |
import { postal, encodePostal, postalPNG } from "etiket"
postal("12345-6789", { type: "postnet" }) // SVG
postal("SN34RD1A", { type: "rm4scc" })
postal("12345678", { type: "auspost", fcc: "59" })
postal("01234567094987654321", { type: "imb", routingCode: "01234567891" })
postalPNG("12345", { type: "postnet" }) // Uint8Array
// Raw bar states: 'T' | 'A' | 'D' | 'F' (4-state), or 1 / 0 (POSTNET, PLANET)
const bars = encodePostal("SN34RD1A", { type: "rm4scc" })barcode() accepts postnet and planet and routes them to the postal
renderer automatically. The per-format raw encoders (encodeRM4SCC,
encodeKIX, encodeAustraliaPost, encodeJapanPost, encodeIMb,
encodePOSTNET, encodePLANET) remain available.
Usage
Barcodes
import { barcode } from "etiket"
barcode("Hello World") // Code 128 (default)
barcode("4006381333931", { type: "ean13", showText: true })
barcode("00012345678905", { type: "itf14", bearerBars: true })
barcode("(01)12345678901234(17)260101", { type: "gs1-128" })
barcode("HELLO", { type: "code39", code39CheckDigit: true })| Option | Type | Default | Description |
| :------------- | :------------------------------ | :------------ | :-------------------------- |
| type | BarcodeType | 'code128' | Barcode format |
| height | number | 80 | Bar height in pixels |
| barWidth | number | 2 | Width multiplier per module |
| color | string | '#000' | Bar color |
| background | string | '#fff' | Background color |
| showText | boolean | false | Show human-readable text |
| textPosition | 'bottom' \| 'top' | 'bottom' | Text position |
| fontSize | number | 14 | Text font size |
| fontFamily | string | 'monospace' | Text font family |
| margin | number | 10 | Margin around barcode |
| marginTop | number | margin | Top margin |
| marginBottom | number | margin | Bottom margin |
| marginLeft | number | margin | Left margin |
| marginRight | number | margin | Right margin |
| textAlign | 'center' \| 'left' \| 'right' | 'center' | Text alignment |
| rotation | 0 \| 90 \| 180 \| 270 | 0 | Barcode rotation |
| bearerBars | boolean | false | Bearer bars (ITF-14) |
| barGap | number | 0 | Extra spacing between bars |
| unit | 'px' \| 'mm' \| 'in' \| 'cm' | 'px' | Measurement unit |
| ariaLabel | string | — | SVG aria-label attribute |
| title | string | — | SVG <title> element |
| desc | string | — | SVG <desc> element |
QR Codes
import { qrcode } from "etiket"
qrcode("https://example.com")
qrcode("Hello", { size: 300, ecLevel: "H", dotType: "rounded" })
// With gradient
qrcode("Test", {
color: {
type: "linear",
rotation: 45,
stops: [
{ offset: 0, color: "#ff0000" },
{ offset: 1, color: "#0000ff" },
],
},
})
// With corner styling
qrcode("Test", {
dotType: "dots",
corners: {
topLeft: { outerShape: "rounded", innerShape: "dots", outerColor: "#ff0000" },
topRight: { outerShape: "extra-rounded" },
bottomLeft: { outerShape: "dots" },
},
})| Option | Type | Default | Description |
| :--------------- | :------------------------------------------------ | :--------- | :--------------------- |
| size | number | 200 | SVG size in pixels |
| ecLevel | 'L' \| 'M' \| 'Q' \| 'H' | 'M' | Error correction level |
| version | number | auto | QR version (1-40) |
| mode | 'numeric' \| 'alphanumeric' \| 'byte' \| 'auto' | 'auto' | Encoding mode |
| mask | 0-7 | auto | Mask pattern |
| color | string \| GradientOptions | '#000' | Module color |
| background | string \| GradientOptions | '#fff' | Background color |
| margin | number | 4 | Quiet zone in modules |
| dotType | DotType | 'square' | Module shape |
| dotSize | number | 1 | Module size (0.1-1) |
| shape | 'square' \| 'circle' | 'square' | Overall QR shape |
| corners | object | — | Finder pattern styling |
| logo | LogoOptions | — | Center logo embedding |
| xmlDeclaration | boolean | false | Add XML declaration |
| unit | 'px' \| 'mm' \| 'in' \| 'cm' | 'px' | Measurement unit |
| ariaLabel | string | — | SVG aria-label |
| title | string | — | SVG <title> element |
| desc | string | — | SVG <desc> element |
Dot types: square, rounded, dots, diamond, classy, classy-rounded, extra-rounded, vertical-line, horizontal-line, small-square, tiny-square
2D Codes
Every 2D, stacked and polychrome symbology has a high-level function returning SVG:
import {
datamatrix,
gs1datamatrix,
pdf417,
micropdf417,
aztec,
microqr,
rmqr,
maxicode,
dotcode,
hanxin,
codablockf,
code16k,
jabcode,
} from "etiket"
datamatrix("Hello World")
gs1datamatrix("(01)12345678901231")
pdf417("Hello World", { ecLevel: 4, columns: 5 })
micropdf417("Hello", { columns: 2 })
aztec("Hello World", { ecPercent: 33 })
microqr("12345", { version: 3 })
rmqr("Hello", { ecLevel: "H" })
maxicode("Hello", { mode: 2, postalCode: "123456789", countryCode: 840 })
dotcode("Hello")
hanxin("Hello", { ecLevel: 3 })
// Stacked linear symbologies (rows taller than modules are wide)
codablockf("Hello World", { columns: 8 })
code16k("Hello World")
// Polychrome
jabcode("Hello", { colors: 8 })Output Formats
import {
barcode,
qrcode,
barcodeDataURI,
qrcodeDataURI,
barcodeBase64,
qrcodeBase64,
qrcodeTerminal,
barcodePNG,
qrcodePNG,
barcodePNGDataURI,
qrcodePNGDataURI,
} from "etiket"
// SVG
const svg = qrcode("Hello") // SVG string
const uri = qrcodeDataURI("Hello") // data:image/svg+xml,...
const b64 = qrcodeBase64("Hello") // data:image/svg+xml;base64,...
const term = qrcodeTerminal("Hello") // Terminal (UTF-8 blocks)
// PNG (zero-dependency raster output — no canvas, no native deps)
const png = qrcodePNG("Hello") // Uint8Array
const pngUri = qrcodePNGDataURI("Hello") // data:image/png;base64,...
const barPng = barcodePNG("12345", { type: "code128" }) // Uint8ArrayPNG output is available for every format except JAB Code, each with a matching
*PNGDataURI variant:
barcodePNG, postalPNG, qrcodePNG, microqrPNG, rmqrPNG,
datamatrixPNG, gs1datamatrixPNG, pdf417PNG, micropdf417PNG, aztecPNG,
maxicodePNG, dotcodePNG, hanxinPNG, codablockfPNG, code16kPNG.
Raw Encoding
encode() returns the underlying data for any symbology without rendering:
import { encode } from "etiket"
const result = encode("Hello", { type: "qr", qr: { ecLevel: "H" } })
if (result.type === "1d")
result.bars // bar/space widths
else if (result.type === "2d")
result.matrix // boolean[][]
else result.bars // postal bar statesConvenience Helpers
import { wifi, email, sms, geo, url, phone, vcard, mecard, event } from "etiket"
wifi("MyNetwork", "password123") // WiFi QR
email("[email protected]") // mailto: QR
sms("+1234567890", "Hello!") // SMS QR
geo(37.7749, -122.4194) // Location QR
url("https://example.com") // URL QR
phone("+1234567890") // tel: QR
// vCard QR
vcard({
firstName: "John",
lastName: "Doe",
phone: "+1234567890",
email: "[email protected]",
org: "Acme Inc",
})
// MeCard QR (simpler, used by Android)
mecard({ name: "John Doe", phone: "+1234567890", email: "[email protected]" })
// Calendar event QR
event({
title: "Meeting",
start: "2026-04-01T10:00:00",
end: "2026-04-01T11:00:00",
location: "Office",
})Batch Generation
import { barcodes, barcodeSheet, qrcodeSheet } from "etiket"
// Many symbols, one call, shared options
const labels = barcodes(["SKU-001", "SKU-002", "SKU-003"], {
type: "code128",
height: 50,
})
// Or a single SVG document holding a grid of them — a label sheet
const sheet = barcodeSheet(
orders.map((o) => o.tracking),
{
type: "code128",
columns: 3,
gap: 12,
labels: orders.map((o) => o.reference),
},
)
// Progress on a long batch
qrcodeSheet(tickets, { columns: 4, onProgress: (done, total) => bar.update(done / total) })Validation
import { validateBarcode, isValidInput, validateQRInput } from "etiket"
validateBarcode("4006381333931", "ean13") // { valid: true }
validateBarcode("ABC", "ean13") // { valid: false, error: '...' }
isValidInput("HELLO", "code39") // trueSwiss QR Code
Generate QR-bill payment codes (mandatory in Switzerland since 2022):
import { swissQR } from "etiket"
swissQR({
iban: "CH4431999123000889012",
creditor: { name: "Max Muster", postalCode: "8000", city: "Zürich", country: "CH" },
amount: 1949.75,
currency: "CHF",
reference: "210000000003139471430009017",
referenceType: "QRR",
})Raw Encoders
Access encoding functions directly for custom rendering:
import {
encodeCode128,
encodeEAN13,
encodeQR,
encodeDataMatrix,
encodePDF417,
encodeAztec,
renderBarcodeSVG,
renderQRCodeSVG,
renderMatrixSVG,
renderBarcodePNG,
renderMatrixPNG,
} from "etiket"
const bars = encodeCode128("data") // number[] (bar/space widths)
const matrix = encodeQR("data") // boolean[][] (QR matrix)
const dm = encodeDataMatrix("data") // boolean[][] (Data Matrix)
// SVG rendering
const svg = renderBarcodeSVG(bars, { height: 100 })
const qrSvg = renderQRCodeSVG(matrix, { size: 400, dotType: "dots" })
// PNG rendering
const png = renderBarcodePNG(bars, { height: 100, scale: 2 })
const qrPng = renderMatrixPNG(matrix, { moduleSize: 10, margin: 4 })Industry Standards
import {
swissQR,
gs1datamatrix,
gs1DigitalLink,
encodeHIBCPrimary,
encodeHIBCSecondary,
} from "etiket"
// Swiss QR-bill (mandatory in Switzerland since 2022)
swissQR({
iban: "CH4431999123000889012",
creditor: { name: "Max Muster", postalCode: "8000", city: "Zürich", country: "CH" },
amount: 1949.75,
currency: "CHF",
})
// GS1 DataMatrix (healthcare, supply chain)
gs1datamatrix("(01)12345678901234(17)260101(10)BATCH01")
// GS1 Digital Link (2027 retail migration)
gs1DigitalLink({ gtin: "09520123456788", batch: "ABC123", serial: "12345" })
// HIBC (medical device labeling, FDA UDI)
const hibc = encodeHIBCPrimary("A123", "PROD456")
barcode(hibc, { type: "code128" }) // Encode in any symbology
// ISBT 128 (blood bank labeling, ISO 7064 Mod 37-2 check character)
const din = encodeISBT128DIN("US", "12345", "26", "000001")
barcode(din, { type: "code128" })
// MaxiCode (UPS shipping labels)
const mc = encodeMaxiCode("Test shipment", {
mode: 2,
postalCode: "12345",
countryCode: 840,
serviceClass: 1,
})SVG Accessibility
All SVG renderers support accessibility attributes out of the box:
barcode("123456789", {
type: "ean13",
ariaLabel: "EAN-13 barcode for product 123456789",
title: "Product Barcode",
desc: "EAN-13 barcode encoding the GTIN 123456789",
})
qrcode("https://example.com", {
ariaLabel: "QR code linking to example.com",
title: "Website QR Code",
})
// CSS currentColor support for theme-aware barcodes
barcode("HELLO", { color: "currentColor", background: "transparent" })Framework Integration
etiket generates plain SVG strings — no DOM required. Works with any framework:
React / Next.js
import { qrcode } from "etiket"
function QRCode({ url }: { url: string }) {
const svg = qrcode(url, {
size: 200,
dotType: "dots",
ecLevel: "H",
corners: {
topLeft: { outerShape: "dots", innerShape: "dots", outerColor: "#ff0000" },
topRight: { outerShape: "dots", innerShape: "dots" },
bottomLeft: { outerShape: "dots", innerShape: "dots" },
},
})
return <div dangerouslySetInnerHTML={{ __html: svg }} />
}Or use a data URI for <img>:
import { qrcodeDataURI } from "etiket"
function QRImage({ url }: { url: string }) {
return <img src={qrcodeDataURI(url)} alt="QR Code" width={200} height={200} />
}PNG output (useful for downloads or <canvas>):
import { qrcodePNGDataURI } from "etiket/png"
function QRCodePNG({ url }: { url: string }) {
return <img src={qrcodePNGDataURI(url, { size: 200 })} alt="QR Code" width={200} height={200} />
}Note:
qrcode()is a pure function with zero DOM dependencies — it works in both Server Components (RSC) and Client Components.
Vue
<script setup lang="ts">
import { qrcode } from "etiket"
const props = defineProps<{ url: string }>()
const svg = computed(() => qrcode(props.url, { dotType: "dots", ecLevel: "H" }))
</script>
<template>
<div v-html="svg" />
</template>Svelte
<script lang="ts">
import { qrcode } from "etiket";
let { url }: { url: string } = $props();
let svg = $derived(qrcode(url, { dotType: "dots", ecLevel: "H" }));
</script>
{@html svg}Angular
import { Component, Input } from "@angular/core"
import { DomSanitizer, SafeHtml } from "@angular/platform-browser"
import { qrcode } from "etiket"
@Component({
selector: "app-qrcode",
template: `<div [innerHTML]="svg"></div>`,
})
export class QRCodeComponent {
svg: SafeHtml = ""
@Input() set url(value: string) {
this.svg = this.sanitizer.bypassSecurityTrustHtml(
qrcode(value, { dotType: "dots", ecLevel: "H" }),
)
}
constructor(private sanitizer: DomSanitizer) {}
}Astro
---
import { qrcode } from "etiket";
const svg = qrcode("https://example.com", { dotType: "dots", ecLevel: "H" });
---
<Fragment set:html={svg} />Features
- Zero dependencies
- Pure ESM, edge-runtime compatible (Cloudflare Workers, Deno, Bun)
- TypeScript-first with strict types (TypeScript 7)
- Tree-shakeable sub-path exports
- CLI tool (
npx etiket) - SVG string output (no DOM required) +
optimizeSVG()for compact inline - PNG raster output (pure JS, zero dependencies) via
etiket/png - SVG accessibility (
ariaLabel,role,title,desc) - Measurement units (
px,mm,in,cm,pt) for print use cases - CSS
currentColorsupport for theme-aware barcodes - Auto EC upgrade to H when QR logo is present (supports PNG, JPEG, SVG, ICO)
- GS1 support (100+ AIs, Digital Link, GS1 QR, GS1 DataMatrix, the full GS1 DataBar family including the stacked variants)
- ECI on QR, Data Matrix, PDF417 and Aztec — non-Latin-1 data is declared, not truncated
- QR kanji mode with the real Shift-JIS table, Structured Append across up to 16 symbols, and optimal multi-segment encoding
- Batch generation and label sheets (
barcodes(),qrcodeSheet()) - HIBC medical device encoding + ISBT 128 blood bank labeling
- Swiss QR-bill payments
- 4-state postal barcodes (RM4SCC, KIX, Australia Post, Japan Post, USPS IMb)
- Works in browser, Node.js, Deno, Bun, Cloudflare Workers
Verification
Producing a symbol is easy; producing one a scanner accepts is not. Every symbology here is checked against something that is not this library:
- Decoded back with zxing-wasm or jsQR — QR, Micro QR, rMQR, Data Matrix, PDF417, MicroPDF417, Aztec, MaxiCode, Code 128, EAN, UPC, Code 39, Code 93, ITF, Codabar, GS1-128 and every GS1 DataBar variant.
- Compared module for module with bwip-js (BWIPP) for the formats no JavaScript decoder implements — Code 16K, Codablock F, DotCode, Han Xin, MSI, Plessey, Code 11, Pharmacode, Identcode, Leitcode, HIBC, POSTNET, PLANET, RM4SCC, KIX, Australia Post, Japan Post and USPS IMb.
- JAB Code is the exception, and says so in its own API docs: no JavaScript or WebAssembly decoder exists and neither zxing nor BWIPP implements it, so its output cannot be verified and is marked experimental.
test/bwip-compare.test.ts keeps the differences visible rather than asserting
them away: a format that diverges is listed with the issue tracking it, and the
test turns red the moment it starts matching.
Comparison
| Feature | etiket | uqr | bwip-js | JsBarcode | qr-code-styling | | :----------------------------------- | :----------------: | :--------------------------------: | :---------------------------------------------: | :-----------------------------------------------: | :--------------------------------------------------------------: | | Zero dependencies | :white_check_mark: | :white_check_mark: | :x: (1.5MB+) | :x: (xmldom) | :x: (qrcode) | | TypeScript-first | :white_check_mark: | :white_check_mark: | :x: | :x: | Partial | | Tree-shakeable | :white_check_mark: | :x: | :x: | :x: | :x: | | 1D barcodes (22 types) | :white_check_mark: | :x: | :white_check_mark: (100+) | :white_check_mark: (13) | :x: | | QR Code (v1-40, all EC) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: | | Data Matrix | :white_check_mark: | :x: | :white_check_mark: | :x: | :x: | | PDF417 | :white_check_mark: | :x: | :white_check_mark: | :x: | :x: | | Aztec Code | :white_check_mark: | :x: | :white_check_mark: | :x: | :x: | | QR dot styling (12 types) | :white_check_mark: | :x: | :x: | :x: | :white_check_mark: | | QR gradients | :white_check_mark: | :x: | :x: | :x: | :white_check_mark: | | QR corner styling | :white_check_mark: | :x: | :x: | :x: | :white_check_mark: | | QR logo embedding | :white_check_mark: | :x: | :x: | :x: | :white_check_mark: | | CLI tool | :white_check_mark: | :x: | :white_check_mark: | :x: | :x: | | Terminal output | :white_check_mark: | :white_check_mark: | :x: | :x: | :x: | | Convenience helpers (WiFi, vCard...) | :white_check_mark: | :x: | :x: | :x: | :x: | | Input validation | :white_check_mark: | :x: | :x: | :x: | :x: | | SVG output | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | PNG output | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Pure ESM | :white_check_mark: | :white_check_mark: | :x: (CJS) | :x: (CJS) | :x: (CJS) | | Bundle size (gzip) | ~24KB | ~12KB | ~160KB | ~15KB | ~30KB+deps |
etiket is the only library that combines 1D barcodes + 2D codes + styled QR codes + SVG & PNG output + zero dependencies + tree-shaking in a single package.
Inspiration & Credits
Built from scratch, inspired by these excellent libraries:
- uqr — Pure SVG QR approach, terminal rendering
- bwip-js — Comprehensive barcode format reference (100+ types)
- JsBarcode — Encoding table validation, barcode rendering patterns
- qr-code-styling — QR styling concepts (dot types, gradients, corners, logos)
Standards: ISO/IEC 15417 (Code 128), ISO/IEC 15420 (EAN/UPC), ISO/IEC 18004 (QR), ISO/IEC 16022 (Data Matrix), ISO/IEC 15438 (PDF417), ISO/IEC 24778 (Aztec), ISO/IEC 24724 (GS1 DataBar), ISO/IEC 16023 (MaxiCode), ISO/IEC 23941 (rMQR), ISO/IEC 20830 (Han Xin), ISO/IEC 23634 (JAB Code), AIM ISS DotCode 4.0 (DotCode), USPS-B-3200 (IMb).
Contributing
Contributions are welcome! Here are some areas where help is especially appreciated:
Encoder improvements needed:
- DotCode — Symbol size selection tables and mask pattern per AIM ISS DotCode 4.0
- Han Xin — Separator bands, data placement, and GB 18030 Chinese character encoding (75% reference match, finders 100%)
- JAB Code — Full LDPC error correction per ISO/IEC 23634
Other contributions:
- Data Matrix DMRE rectangular sizes (#71)
- GS1 DataBar stacked variants (#61)
- Round-trip scan tests for experimental formats
- Documentation improvements
pnpm install # Install dependencies
pnpm dev # Run tests in watch mode
pnpm test # Lint + typecheck + test
pnpm build # Build for productionLicense
Published under the MIT license.
