QR code color combinations that scan
We rendered 38 foreground and background colour pairs as real QR codes and ran each one through a QR decoder at four sizes, then blurred, shrunk and noisy. Here is what decoded, with each pair's contrast ratio and hex codes.
Every result below comes from jsQR, the open-source decoder inside our QR Code Color tool, reading a clean digital image. Phone cameras deal with glare, focus, printing and lighting, and their apps use other decoders. A pass here is a good sign, not a guarantee. A fail here is a strong warning. Test your final code with a few real phones before you print it.
Results for 38 colour pairs
Pass means jsQR returned the exact encoded text. "Noise" shows how many of 3 noise patterns decoded, and it only counts as a pass at 3/3. "Read inverted" means jsQR only read the code on its second, inverted pass (light modules on a dark background).
| QR | Pair | Contrast | 400 px | 200 px | 120 px | 80 px | Blurred | Shrunk | Noise | Verdict |
|---|---|---|---|---|---|---|---|---|---|---|
Black on white#000000 on #ffffff | 21.00:1 | Pass | Pass | Pass | Pass | Pass | Pass | 3/3 | Decodes in every test | |
Dark grey on white#4b5563 on #ffffff | 7.56:1 | Pass | Pass | Pass | Pass | Pass | Pass | 3/3 | Decodes in every test | |
Mid grey on white#9ca3af on #ffffff | 2.54:1 | Fail | Pass | Pass | Pass | Pass | Pass | 0/3 | Decodes in some tests | |
Light grey on white#d1d5db on #ffffff | 1.47:1 | Fail | Fail | Fail | Fail | Fail | Fail | 0/3 | Fails every clean size | |
Navy on white#1e3a8a on #ffffff | 10.36:1 | Pass | Pass | Pass | Pass | Pass | Pass | 2/3 | Decodes in some tests | |
Violet on white#5b3df5 on #ffffff | 6.12:1 | Pass | Pass | Pass | Pass | Pass | Pass | 2/3 | Decodes in some tests | |
Dark green on white#166534 on #ffffff | 7.13:1 | Pass | Pass | Pass | Pass | Pass | Pass | 3/3 | Decodes in every test | |
Red on white#dc2626 on #ffffff | 4.83:1 | Pass | Pass | Pass | Pass | Pass | Pass | 2/3 | Decodes in some tests | |
Magenta on white#d946ef on #ffffff | 3.46:1 | Pass | Pass | Pass | Pass | Pass | Pass | 1/3 | Decodes in some tests | |
Coral on white#ff6b4a on #ffffff | 2.82:1 | Pass | Pass | Pass | Pass | Pass | Pass | 2/3 | Decodes in some tests | |
Orange on white#f97316 on #ffffff | 2.80:1 | Pass | Pass | Pass | Pass | Pass | Pass | 1/3 | Decodes in some tests | |
Lime on white#84cc16 on #ffffff | 1.98:1 | Fail | Pass | Pass | Pass | Fail | Fail | 0/3 | Decodes in some tests | |
Gold on white#d4af37 on #ffffff | 2.10:1 | Fail | Pass | Pass | Pass | Fail | Fail | 0/3 | Decodes in some tests | |
Yellow on white#facc15 on #ffffff | 1.53:1 | Fail | Fail | Fail | Fail | Fail | Fail | 0/3 | Fails every clean size | |
Pastel pink on white#f9a8d4 on #ffffff | 1.81:1 | Fail | Pass | Pass | Fail | Fail | Fail | 0/3 | Decodes in some tests | |
Light blue on white#93c5fd on #ffffff | 1.80:1 | Fail | Pass | Pass | Fail | Fail | Fail | 0/3 | Decodes in some tests | |
Mint on white#a7f3d0 on #ffffff | 1.28:1 | Fail | Fail | Fail | Fail | Fail | Fail | 0/3 | Fails every clean size | |
Black on yellow#000000 on #facc15 | 13.71:1 | Pass | Pass | Pass | Pass | Pass | Pass | 3/3 | Decodes in every test | |
Black on gold#000000 on #d4af37 | 9.99:1 | Pass | Pass | Pass | Pass | Pass | Pass | 3/3 | Decodes in every test | |
Black on light grey#000000 on #d1d5db | 14.25:1 | Pass | Pass | Pass | Pass | Pass | Pass | 3/3 | Decodes in every test | |
Black on mid grey#000000 on #9ca3af | 8.27:1 | Pass | Pass | Pass | Pass | Pass | Pass | 2/3 | Decodes in some tests | |
Navy on cream#1e3a8a on #fef3c7 | 9.30:1 | Pass | Pass | Pass | Pass | Pass | Pass | 3/3 | Decodes in every test | |
Brown on beige#78350f on #f5f5dc | 8.20:1 | Pass | Pass | Pass | Pass | Pass | Pass | 3/3 | Decodes in every test | |
Dark teal on mint#134e4a on #ccfbf1 | 8.41:1 | Pass | Pass | Pass | Pass | Pass | Pass | 3/3 | Decodes in every test | |
Purple on lavender#5b21b6 on #ede9fe | 7.57:1 | Pass | Pass | Pass | Pass | Pass | Pass | 3/3 | Decodes in every test | |
Dark red on pink#7f1d1d on #fecdd3 | 7.10:1 | Pass | Pass | Pass | Pass | Pass | Pass | 2/3 | Decodes in some tests | |
Grey on grey#6b7280 on #9ca3af | 1.90:1 | Fail | Pass | Pass | Pass | Fail | Fail | 0/3 | Decodes in some tests | |
Pink on light blue#f9a8d4 on #bfdbfe | 1.28:1 | Fail | Fail | Fail | Fail | Fail | Fail | 0/3 | Fails every clean size | |
Blue on red#2563eb on #dc2626 | 1.07:1 | Fail | Fail | Fail | Fail | Fail | Fail | 0/3 | Fails every clean size | |
Green on red#16a34a on #dc2626 · read inverted | 1.47:1 | Fail | Fail | Fail | Pass | Fail | Fail | 0/3 | Decodes in some tests | |
White on black#ffffff on #000000 · read inverted | 21.00:1 | Pass | Pass | Pass | Pass | Pass | Pass | 3/3 | Decodes in every test | |
White on navy#ffffff on #1e3a8a · read inverted | 10.36:1 | Pass | Pass | Pass | Pass | Pass | Pass | 3/3 | Decodes in every test | |
White on red#ffffff on #dc2626 · read inverted | 4.83:1 | Pass | Fail | Pass | Pass | Fail | Pass | 2/3 | Decodes in some tests | |
Yellow on navy#facc15 on #1e3a8a · read inverted | 6.76:1 | Pass | Fail | Pass | Pass | Pass | Pass | 2/3 | Decodes in some tests | |
Gold on black#d4af37 on #000000 · read inverted | 9.99:1 | Pass | Pass | Pass | Pass | Pass | Pass | 2/3 | Decodes in some tests | |
Cyan on black#22d3ee on #000000 · read inverted | 11.62:1 | Pass | Pass | Pass | Pass | Pass | Pass | 1/3 | Decodes in some tests | |
Red on black#dc2626 on #000000 · read inverted | 4.35:1 | Pass | Pass | Pass | Pass | Pass | Pass | 0/3 | Decodes in some tests | |
Dark grey on black#374151 on #000000 · read inverted | 2.04:1 | Pass | Pass | Pass | Pass | Pass | Pass | 0/3 | Decodes in some tests |
What we saw
- Very low contrast failed. All 5 pairs that failed at every clean size had a contrast ratio of 1.53:1 or less, for example yellow on white and blue on red.
- Dark colours on a light background were safest. Every dark-on-light pair with a contrast ratio of 2.8:1 or more decoded at all four clean sizes.
- Contrast alone didn't predict the stress tests. The 12 pairs that passed all 7 tests had contrast of 7.13:1 or more, but some high-contrast pairs still missed a noise pattern, for example navy on white (10.36:1).
- Pale pairs did worse at the biggest size. Mid grey, lime, gold, pastel pink and light blue on white, and grey on grey, failed at 400 px but decoded at 200 px. We haven't worked out why jsQR behaves this way. It is one more reason not to trust a single size.
- Light-on-dark codes needed the inverted pass. jsQR read them only when it tried the image inverted. Not every phone scanner app reads inverted codes, so dark on light is the safer choice.
These patterns hold for these 38 pairs, in this test. We don't claim a safe contrast threshold.
How we tested
- Encode. The text
https://crispkit.app/qr-code-color/with error correction M. That makes a version 3 code: 29 × 29 modules plus a 4-module quiet zone. - Render. Each pair is drawn by the same QR engine our QR Code Color tool uses, at 400, 200, 120 and 80 px wide. That is about 10.8, 5.4, 3.2 and 2.2 pixels per module.
- Degrade. Three extra tests. Blurred: the 200 px image is blurred (a three-pass box blur of radius 1 px). Shrunk: the 400 px image is shrunk to 100 px and enlarged back. Noise: the blurred 200 px image gets random noise of up to ±40 levels per channel. The noise uses 3 fixed seeds, the same for every pair.
- Decode. jsQR 1.4.0 reads each image, normal first and then inverted. A test passes only if the decoded text matches exactly.
- Contrast. The WCAG 2 contrast ratio of the two colours, from 1:1 (same colour) to 21:1 (black on white).
- Repeat. All image processing is plain JavaScript with seeded noise, so a re-run in the same browser gives the same results. We ran it twice and got identical results. You can re-run it above. The test script is qr-color-test.js.
Limits: one decoder, one payload length, flat colours only (no gradients or logos), and no print, camera or lighting effects. A pair that decodes here can still fail on paper under poor light.
Questions
Can a QR code be any colour?
It can be many colours, but the dark modules need to stand out clearly from the background. In our test, all 5 pairs that failed at every clean size had a contrast ratio of 1.53:1 or less, while every dark-on-light pair at 2.8:1 or more decoded at all four clean sizes.
Do inverted (white on black) QR codes scan?
jsQR read each of our light-on-dark pairs in at least some tests, but only on its inverted pass. Support for inverted codes varies between phone scanner apps, so test with the phones your audience uses, or keep dark modules on a light background.
Why do yellow and pastel QR codes fail?
They are too close in brightness to a white background. Yellow on white (1.53:1) and mint on white (1.28:1) failed at every size we tried. Contrast ratio measures brightness difference, and that difference is what decoders rely on.
Is this the same as testing with my phone?
No. This is a software decoder reading clean digital images. It is a quick filter for colour choices, not a substitute for scanning your printed code with real phones.
Guide: Can a QR code be any color?