QR code error correction and contrast: choosing L, M, Q or H and colours that scan

Updated 5 min read

The usual failure is a code that scanned fine in the preview and does nothing once it is printed on a flyer, a sticker or a coloured box. The causes are nearly always the same few settings: error correction that is too low for the surface, colours that look distinct to a person but barely differ to a camera, or a payload so dense that each module prints too small to resolve.

What error correction actually buys

A QR code stores its data as bytes called codewords, and adds extra codewords computed with a Reed-Solomon code. If some modules are scratched, smudged or covered, the decoder can reconstruct the missing codewords from the redundant ones. The four levels choose how much redundancy to add:

LevelRecovers roughlyTypical use
L7% of codewordsScreens, clean print, maximum text
M15%The balanced default
Q25%Labels that get handled or dirty
H30%Harsh environments, codes with something printed over them

Read “recovers roughly” literally. The figure counts codewords, not the visible area, and a bite out of the three large corner squares (the finder patterns) or the strips that record the code’s own format costs more than the same area of plain data would. Level H does not make a code survive losing a third of its picture; it gives you more headroom than L, nothing more.

The price: capacity, and therefore module size

Redundancy takes space. In byte mode, the largest QR version (40, a 177 × 177 grid) holds at most 2953 bytes at L and 1273 at H. The generator on this site measured the levels in between: 2331 at M and 1663 at Q. The same trade happens at small sizes. Version 1, a 21 × 21 grid, holds 17 bytes at L and only 7 at H, so a 10-byte string fits version 1 at L but is pushed up to a 25 × 25 grid at H.

A bigger grid matters for print because the code’s physical width is fixed. A version-2 code has 25 modules per side, a version-3 code has 29, and the same square of paper is cut into smaller cells. Smaller modules are harder for a camera to resolve from a distance or on rough stock. A longer URL also has this effect: it raises the version and shrinks every module. Two ways to buy room:

  • Shorten the payload. Drop tracking parameters and use a short path. Saving enough bytes drops a version, which is worth more than most colour or level tweaks.
  • Count bytes, not characters. A Latin letter is 1 byte in UTF-8, a Chinese character is 3. A 984-character Chinese message already fills a level-L code in this encoder. Long runs of digits pack far tighter: the same encoder measured 7089 digits at L.

The generator tells you when you cross the limit, giving the byte count and the maximum for the chosen level, and suggests a lower level.

Choosing a level

SituationStarting point
Shown on a phone or monitor, read from close rangeL or M
Printed on paper or a screen-printed labelM or Q
Outdoors, on packaging, stickers on curved or handled surfacesQ or H
Long payload that barely fitsThe lowest level that holds it, then test

H on a short link shown on a clean web page only costs you module size. L on something that will be scuffed leaves too little headroom for the first scratch.

Contrast: what the measurement is and what it is not

Scanners find the dark and light modules by their difference in brightness, so what matters is the luminance difference between the two colours, not hue. The generator measures it with the contrast ratio that accessibility guidance uses, from 1:1 (identical) to 21:1 (black on white). Its rules are:

  • Below 3:1 the preview still appears, but the PNG and SVG exports and the copy-to-clipboard action are refused.
  • From 3:1 to 4.5:1 you get a warning to test with a real phone before printing.
  • Above 4.5:1 it reports the ratio and moves on.

Some colours that people like are worse than they look. Pure yellow on white is about 1.07:1. Pure red on white is about 4.0:1, which scans but with little margin. A mid-grey such as #999999 on white is about 2.85:1, below the threshold. Black on white is 21:1.

Two limits of this check are worth knowing. First, it measures brightness difference, not which colour is darker. Many scanners expect dark modules on a light background and fail on inverted codes, and some accept both, so a light-on-dark code can pass the ratio test and still fail on a given phone. Second, the default colours come from this site’s theme tokens, so in dark mode they can come out as light modules on a dark background. Check which colour is in which field before you export, and set them to a dark module colour on a light background for anything you print.

Printing changes contrast too. Glossy paper reflects light into the camera, light ink loses density on rough stock, and a screen at low brightness is not the same as a printed page. A ratio computed from two hex colours cannot see any of that, so a phone test on the real printed object is the check that counts.

The quiet zone

A code needs a blank border so the scanner can tell where it starts. The QR standard asks for four modules, and the tool defaults to 4 and allows 0 to 8. Setting it to 0 to save space is a common cause of codes that scan from a screen but fail on a printed page, where the edge of a coloured box or a photo runs right up to the code. If the code sits on a coloured background, make the light colour a real background and keep the border; do not paint the code straight onto an image.

Size and file format

The generator picks a whole-number number of pixels per module, so every module is the same size and edges stay sharp. The size field is therefore a target: the status line reports the actual width, which is the module count plus the border times the integer scale. For print, prefer SVG. It is vector, so it scales to any size without blurring module edges, while a PNG is fixed in pixels.

Detect mode recognises a scheme such as https://; a link needs the scheme and a host name with a dot or the tool reports it as incomplete, since a camera would otherwise treat it as plain text. For Wi-Fi, the common format is a plain string:

WIFI:T:WPA;S:MyNetwork;P:mypassword;;

Characters such as ; , : and \ inside the name or password are escaped with a backslash in this format. Not every scanner app joins the network automatically, so test it on the devices you expect.

Encoding happens on this page. The encoder library is downloaded once and cached, after which the text you type is not sent anywhere.

Open the tool: QR Code Generator

Back to guides

More guides