Skip to content
Devix Open Source

Guide

Logos, colours and shapes

A logo, safely

createQrCode(el, { value: 'https://devix.pk', logo: { src: '/logo.svg' } });
Option Default
src — A URL, a data: URI, or SVG markup starting with <svg.
size 0.22 Share of the code's width, capped at 0.3.
padding 1 Light modules around it.
clear true Punch the modules out. Turn it off only if the logo is transparent.

Why the size is capped

A QR code's error correction can rebuild a proportion of the data — nominally 7% at level L and 30% at level H. Those figures describe damage scattered across the code, such as a scratch or a smudge. A logo is a solid square in the middle, and the data is interleaved across blocks, so a square hole destroys whole codewords in a handful of blocks rather than a little of each. The usable share is therefore much smaller than the headline number.

We measured it: codes at every level were covered by a growing square and decoded until they failed. The largest square that survived everywhere was about a third of the nominal tolerance. That is what fitLogo() uses, and it is deliberately the conservative end — telling someone their code will scan when it will not is the failure that matters.

import { encode, fitLogo } from '@devix-labs/qr-code';

fitLogo(encode('https://devix.pk', { level: 'H' }), { src: 'x', size: 0.3 });
// { safe: false, maxSize: 0.21, fraction: 0.144, tolerance: 0.3, covered: 121 }

The widget does this for you: with fitLogoLevel on (the default) it raises the level to H before giving up, and only warns if even H is not enough.

Practical advice

  • Keep the logo square-ish. A wide logo wastes the budget on the modules above and below it.
  • A logo with a solid background reads better than a transparent one, because the punched-out area is already light.
  • Test the printed size. A code that scans at 400px on a screen may not at 2cm on a receipt, logo or no logo.

Colours

createQrCode(el, { value: 'x', dark: '#18181b', light: '#fafafa' });
createQrCode(el, { value: 'x', dark: '#3a86ff', light: 'none' });  // transparent

Contrast is what a scanner needs, and dark on light is what it expects. Inverting — light modules on a dark background — fails on many readers, so if you want a dark code on a dark page, keep the light modules light and give the code its own pale panel.

A safe rule: at least 40% contrast between the two, and never a light dark colour. Pale grey on white is the most common reason a "designed" code does not scan.

Module shapes

createQrCode(el, { value: 'x', shape: 'rounded' });
createQrCode(el, { value: 'x', shape: 'dot', finderShape: 'square' });

square is the shape every reader was built for. rounded is safe in practice. dot leaves gaps between modules and is the riskiest — it looks best and scans worst, so keep the error correction high and test it.

Shaping the modules but not the three corner finders — shape: 'dot' with finderShape: 'square' — is the combination that stays most reliable, because the finders are what a reader locates first.

Size and the quiet zone

createQrCode(el, { value: 'x' });               // fluid: fills its container
createQrCode(el, { value: 'x', size: 220 });    // fixed at 220px
createQrCode(el, { value: 'x', quiet: 2 });     // a narrower border

The four-module quiet zone is part of the specification, not decoration: readers use it to find the edges. Narrowing it to save space is the second most common reason a code does not scan. If you need the code tighter to its box, set quiet: 0 and give the element its own light padding in CSS instead — the border is then still there, it is just made of page rather than of SVG.

Updated 15 Sep 2026