Common Low Contrast Text in Qr Code Apps: Causes and Fixes

Low contrast text in QR‑code scanners typically stems from three overlapping issues:

June 20, 2026 · 5 min read · Common Issues

What causes low contrast text in QR code apps (technical root causes)

Low contrast text in QR‑code scanners typically stems from three overlapping issues:

  1. Hard‑coded color palettes – Many QR‑code libraries (e.g., ZXing, ZBar) expose only the matrix pattern. Developers often overlay UI elements (instruction labels, result text, buttons) using static hex values that were chosen for a light theme and never adjusted for dark mode or varying device brightness.
  1. Dynamic background without adaptive foreground – QR‑code views frequently display the scanned image as a background. When the camera feed contains bright areas (e.g., white paper, sunlight) the overlaid text inherits the same luminance, dropping the contrast ratio below WCAG 2.1 AA’s 4.5:1 threshold for normal text.
  1. Missing contrast‑checking in the build pipeline – Teams rely on visual QA on a handful of devices. Automated linting or unit‑test contrast checks are absent, so regressions slip in when a new theme, font size, or localization string changes the text color or background opacity.

These root causes are amplified in QR‑code apps because the UI is minimal: a large camera preview, a small “Scan” button, and a result dialog. Any contrast flaw becomes immediately visible to users trying to read the decoded URL or product info.

Real‑world impact (user complaints, store ratings, revenue loss)

Specific examples of how low contrast text manifests in QR code apps

#ManifestationTypical UI locationWhy it fails contrast
1Result URL label – light gray (#CCCCCC) on a white camera previewBottom‑aligned toast or dialog after a successful scanGray on white yields ~2.1:1 ratio
2Scan button icon tint – dark gray (#424242) on a dim indoor previewOverlay button in the center of the previewIn low‑light scenes, button blends with dark background
3Permission rationale text – white (#FFFFFF) on a semi‑transparent black overlay (rgba(0,0,0,0.4))Runtime permission rationale screenEffective background luminance ~0.6, white gives ~3.2:1
4Error message – red (#E53935) on a red‑tinted preview (e.g., scanning a red QR code)Inline error under the camera viewSimilar hue reduces perceived contrast
5Instruction hint – light teal (#80CBC4) on a gradient background that shifts from teal to whiteOnboarding carousel slidesGradient creates local areas where teal on teal is <3:1
6Accessibility label – invisible (color set to android:color/transparent) but still announced by TalkBackHidden “Scan again” button for voice navigationWhile invisible to sighted users, the label’s contrast is irrelevant; the real issue is missing visible fallback
7Result copy‑to‑clipboard toast – dark text on a dark‑mode toolbar background (#212121)Temporary toast after long‑press on resultDark on dark yields ~2.8:1

How to detect low contrast text (tools, techniques, what to look for)

  1. Automated contrast auditors – Run axe-core (via the susatest-agent CLI) against the compiled APK or a web‑URL version of the app. SUSA’s accessibility module evaluates every visible text node against WCAG 2.1 AA, reporting failures with exact contrast ratios and the responsible CSS/Android style attributes.
  1. Persona‑based dynamic testing – Enable the “elderly” and “low vision” personas in SUSA. These personas simulate reduced contrast sensitivity and increased glare, causing the explorer to deliberately overlay UI elements on varied camera feeds (bright, dim, patterned) to surface contrast‑dependent failures that static analysis misses.
  1. Camera feed simulation – Use SUSA’s built‑in scene library to feed the app with predefined images (e.g., a white sheet, a newspaper, a dark‑room QR code). The explorer captures screenshots at 30 fps and runs a contrast checker on each frame, flagging any frame where the ratio drops below 4.5:1 for normal text or 3:1 for large text.
  1. Manual spot checks – When a failure is reported, inspect the UI hierarchy:

Verify that either the text color or the background is adjustable based on theme or current frame luminance.

  1. CI gate – Add the susatest-agent step to your GitHub Actions workflow:
  2. 
       - name: Run SUSA accessibility scan
         uses: susatest/susatest-agent@v1
         with:
           apk: ./app/build/outputs/apk/debug/app-debug.apk
           personas: elderly,low-vision
           fail-on: wcag2aa
    

The job fails if any contrast violation is detected, preventing a merge.

How to fix each example (code‑level guidance)

#Fix
1Result URL label – Use a theme‑aware color: ?attr/colorOnSurface (Material) or define a colorResultText that switches between #212121 (light) and #FFFFFF (dark). In XML: .
2Scan button icon tint – Apply ripple with adaptive tint: ?attr/colorControlNormal. Ensure the button’s background is ?attr/selectableItemBackgroundBorderless which adapts to light/dark.
3Permission rationale text – Increase overlay opacity to rgba(0,0,0,0.6) or switch text to ?attr/colorOnSurface. In a Dialog, set android:background="?attr/colorSurface" and text color accordingly.
4Error message – Add a semi‑transparent backdrop behind the text: . Or compute luminance of the preview frame (via CameraX.ImageAnalysis) and dynamically choose black or white error text.
5Instruction hint – Avoid placing text over gradients. Use a solid scrim: behind the hint, or restrict the hint to a container with a fixed background color (?attr/colorSurface).
6Accessibility label – Provide a visible fallback: set android:contentDescription *and* ensure the button has a minimum contrast background (?attr/colorPrimary). If you need an invisible hit‑target, overlay a transparent view with a visible label for TalkBack only (android:importantForAccessibility="yes").
7Result copy‑to‑clipboard toast – Use the system toast style (Toast.makeText(context, text, Toast.LENGTH_SHORT).show()) which respects the current theme, or create a custom toast with background ?attr/colorSurface and text ?attr/colorOnSurface.

Prevention: how to catch low contrast text before release

  1. Shift‑left contrast lint – Add a custom lint rule (or use androidx.annotation with ColorInt) that flags any hard‑coded color value not referencing a theme attribute. Fail the build on violations.
  1. Automated theme swap tests – In your unit/UI test suite, run the same screen under both Theme.Material3.Light and Theme.Material3.Dark. Assert that every TextView meets a minimum contrast ratio using a library like androidx.core.graphics.ColorUtils.
  1. Continuous accessibility scanning – Integrate SUSA’s CLI into nightly builds:
  2. 
       susatest scan --apk app.apk --personas elderly,low-vision --wcag 2.1 AA
    

The report is uploaded as an artifact; a GitHub Action can block PRs if new contrast issues appear.

  1. Design system enforcement – Define a single set of text styles (e.g., bodyLarge, labelMedium) in your theme with prescribed color tokens. Any UI component must consume these tokens; design reviewers check compliance via a Figma plugin that extracts token usage.
  1. Runtime monitoring – Ship a lightweight observer that logs the contrast ratio of any TextView rendered over a camera preview (using CameraX preview analysis). If the ratio falls below a threshold for >2 seconds, fire a non‑fatal crashlytics event. This catches edge cases missed in pre‑release testing (e.g., user‑specific lighting conditions).

By embedding these checks into the development loop—lint, unit tests, CI, and runtime guards—you ensure that low‑contrast text never reaches users, preserving both accessibility compliance and the star ratings that drive downloads for QR‑code scanners.

Test Your App Autonomously

Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.

Try SUSA Free