यन्त्रम् · ANGULAR 22+
Styled QR codes as SSR-safe SVG. Zero runtime dependencies.
npm i @code_with_sachin/ngx-style-qrMost QR libraries draw to a canvas in the browser, which means an empty box during server-side rendering and a pop when hydration catches up. This one encodes and draws in pure functions, so the symbol is already in the server-rendered HTML. The QR encoder — Reed–Solomon, masking, all 40 versions — is written into the package, so there is nothing to install alongside it.
TESTED AGAINST
| NAME | TYPE | DEFAULT | NOTES |
|---|---|---|---|
| @angular/core | ^22.0.0 | 22.0.5 | Standalone APIs, signal inputs and afterNextRender are all required. |
| typescript | ~6.0.0 | 6.0.3 | Whatever your Angular version supports. |
| node | >=20 | 24.15.0 | Build and SSR only. |
Peer ranges are wider than this — the table lists the exact versions the demos on this page are running, so you have a known-good combination to fall back on.
Nothing to register. This package exposes no provider function and reads no injection token — import the component where you use it and you are done. Your app.config.ts needs no change.
Everything here is standalone, but standalone components and directives are importable from an @NgModule — put them in the module's imports, not declarations. No importProvidersFrom is needed: @NgModule.providers is typed Array<Provider | EnvironmentProviders>, so the provide*() functions drop straight in.
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { StyleQrComponent } from '@code_with_sachin/ngx-style-qr';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
// Standalone components and directives go in `imports`.
imports: [
BrowserModule,
StyleQrComponent,
],
bootstrap: [AppComponent],
})
export class AppModule {}Standalone component — import it where you use it. No module, no provider.
import { Component } from '@angular/core';
import { StyleQrComponent } from '@code_with_sachin/ngx-style-qr';
@Component({
selector: 'app-share',
imports: [StyleQrComponent],
template: `<ngx-style-qr data="https://sachinsingh.me" />`,
})
export class Share {}Every input below maps one-to-one to a template binding. Scan the result with your phone as you change things — that is the only test that counts.
Read as a data URI, so PNG export keeps working.
<NGX-STYLE-QR>
| NAME | TYPE | DEFAULT | NOTES |
|---|---|---|---|
| data | string | — | Required. Numeric, alphanumeric and UTF-8 byte modes are picked automatically. |
| size | number | 256 | Rendered width/height in CSS pixels. The SVG scales losslessly, so this is presentation only. |
| ecLevel | 'L' | 'M' | 'Q' | 'H' | 'M' | Error correction: ~7% / 15% / 25% / 30% recoverable. Use H whenever a logo is set. |
| margin | number | 4 | Quiet zone in modules. The spec requires 4; lower hurts scan rates. |
| moduleShape | 'square' | 'dots' | 'rounded' | 'smooth' | 'square' | smooth is neighbour-aware — runs of modules merge into continuous blobs. |
| eyeShape | 'square' | 'rounded' | 'circle' | 'leaf' | 'square' | Applied to all three finder patterns. |
| color | string | QrGradient | '#000000' | Body modules. Any CSS colour, or a linear/radial gradient. |
| bgColor | string | QrGradient | 'transparent' | 'transparent' | Full-canvas background, quiet zone included. |
| eyeColor | string | QrGradient | null | null | Finder rings. Falls back to color. |
| eyeInnerColor | string | QrGradient | null | null | Finder centres. Falls back to eyeColor, then color. |
| logo | QrLogo | null | null | Centre image with automatic module erasure. Use a data URI. |
| label | string | '' | Accessible name emitted as <title>. Falls back to data. |
Colours accept a CSS colour string or a gradient object. Gradient ids are derived from the gradient's own content rather than a counter, so the server and client renders agree and url(#…) survives hydration.
<ngx-style-qr
data="https://sachinsingh.me"
ecLevel="H"
moduleShape="smooth"
eyeShape="rounded"
[size]="320"
[color]="{
type: 'linear',
rotate: 45,
stops: [{ offset: 0, color: '#c21b2e' }, { offset: 1, color: '#f4771f' }]
}"
bgColor="#0a0a0a"
eyeColor="#c9a24b"
label="Scan for my portfolio"
/> The modules under the logo are erased, not covered — that comes straight out of the error-correction budget, so pair a logo with ecLevel="H". The component warns in dev mode if you don't.
// A data URI, not a URL — a cross-origin image taints the export
// canvas and makes download(..., 'png') reject with a SecurityError.
protected readonly logo = {
src: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0i…',
size: 0.22, // fraction of the symbol, clamped to 0.05–0.35
padding: 1, // extra modules cleared around it
bgColor: '#fff', // optional plate behind the image
bgRadius: 2,
};@Component({
template: `
<ngx-style-qr #code data="https://sachinsingh.me" [size]="512" />
<button (click)="code.download('portfolio', 'png')">PNG</button>
<button (click)="code.download('portfolio', 'svg')">SVG</button>
`,
})
export class Share {}
// Or from TypeScript:
readonly code = viewChild.required(StyleQrComponent);
save() { void this.code().download('portfolio', 'png'); }The encoder and renderer are exported on their own, for painting a matrix into a canvas, a terminal, or a print pipeline.
import { encodeQr, buildSvgModel } from '@code_with_sachin/ngx-style-qr';
const qr = encodeQr('https://sachinsingh.me', 'H');
qr.version; // 3
qr.size; // 29 (modules per side)
qr.modules[0][0]; // true — top-left finder pattern
// …then paint it however you like: canvas, terminal, PDF, print pipeline.
for (const row of qr.modules) {
console.log(row.map((dark) => (dark ? '██' : ' ')).join(''));
}METHODS & FUNCTIONS
| NAME | TYPE | DEFAULT | NOTES |
|---|---|---|---|
| download(filename?, format?) | (string, 'svg' | 'png') => Promise<void> | ('qr-code', 'png') | Saves the rendered code. No-op during SSR. PNG rasterises at 2× size. |
| encodeQr(data, ecLevel?) | (string, QrEcLevel) => QrCode | ('M') | Standalone encoder. Throws if the payload exceeds version 40 at that level. |
| buildSvgModel(qr, options) | (QrCode, StyleQrOptions) => QrSvgModel | — | Pure renderer — path data, gradient defs and logo box. No DOM. |
encodeQr throws — with the byte count and the limit — if the payload cannot fit version 40: roughly 2953 bytes at level L, 1273 at level H. Kanji mode is not implemented.
The SVG always carries role="img" and a <title>. Set label when the payload is a URL a screen-reader user would not want read out character by character.
Rasterised through an offscreen canvas at 2× size. A cross-origin logo taints that canvas, so keep logo.src a data URI. A transparent bgColor stays transparent in the PNG.
The dots shape draws circles slightly larger than one module. Tangent circles cover only π/4 of a cell, which drops the local dark ratio far enough that adaptive binarisers start misreading whole blocks.