Common Dark Mode Bugs and How to Catch Them

Common Dark Mode Bugs and How to Catch Them

January 02, 2026 · 18 min read · Common Issues

Common Dark Mode Bugs and How to Catch Them

Dark mode is no longer a novelty; it is an expectation for modern apps and websites. Yet even teams that ship polished light‑mode experiences frequently release dark‑mode builds riddled with contrast failures, hard‑coded colors, and subtle UI glitches that only appear when the system theme flips. This guide walks through the most common dark‑mode defects, explains why they arise, shows how they look to real users, and gives concrete steps to reproduce, detect, fix, and prevent each one. A test matrix compares manual and automated techniques, and a later section demonstrates how persona‑driven autonomous exploration (the approach behind SUSA) surfaces issues that scripted tests often miss.

---

1. Understanding Dark Mode Fundamentals

Before diving into bugs, it helps to recall how dark mode is implemented across platforms.

1.1 Color Tokens vs. Hardcoded Values

Design systems expose semantic tokens such as --color-background, --color-primary, or colorSurface that map to light‑ and dark‑mode palettes. When developers replace those tokens with literal hex values (#FFFFFF, #000000) the theme switch cannot update the UI.

1.2 System‑Level Media Queries and Qualifiers

If any of these mechanisms are bypassed, the UI stays locked in light mode or shows a mismatched blend.

1.3 Contrast Requirements

WCAG 2.1 AA demands a minimum contrast ratio of 4.5:1 for normal text and 3:1 for large text. Dark mode often reduces contrast because designers mistakenly assume dark backgrounds automatically improve readability.

1.4 Persona‑Driven Exploration

Autonomous QA agents that simulate curious, impatient, elderly, or power‑user personas interact with the app exactly as a human would, toggling system settings, navigating deep links, and triggering edge‑case flows. Because they do not rely on pre‑written scripts, they discover dark‑mode defects that only appear after a specific sequence of actions (e.g., opening a modal after switching theme while a network request is pending).

---

2. Bug Pattern #1: Insufficient Contrast

2.1 Why It Happens

Designers copy light‑mode color values into dark mode without adjusting luminance, or they rely on default system colors that fail contrast checks on certain OLED panels.

2.2 What the User Sees

Gray text on a slightly lighter gray background becomes unreadable, especially in bright ambient light or for users with low vision. Icons may blend into toolbars, making buttons appear invisible.

2.3 How to Reproduce

  1. Enable dark mode at the OS level.
  2. Open each screen and use a contrast‑checking tool (e.g., Chrome DevTools Contrast checker, Android Studio’s Layout Inspector, or Xcode’s Accessibility Inspector).
  3. Note any element where the ratio falls below 4.5:1.

2.4 How to Detect Automatically

2.5 Fix

Replace hardcoded colors with theme‑aware tokens. If a token does not exist, create one and define both light and dark values that satisfy the contrast ratio.

2.6 Prevent

Add a contrast‑validation step to the design hand‑off (e.g., a Figma plugin that flags low‑contrast pairs) and enforce it in CI via a custom lint rule that scans CSS/Android XML/iOS Asset Catalogs for literal colors used in text or icon layers.

---

3. Bug Pattern #2: Hardcoded Backgrounds or Borders

3.1 Why It Happens

Developers sometimes set a view’s background color directly in code (view.backgroundColor = .white) or in XML (android:background="#FFF"). When the system switches to dark mode, those views stay white, creating a stark “flash” or a white rectangle inside a dark panel.

3.2 What the User Sees

White cards, input fields, or dividers that look out of place, causing visual noise and making the interface feel unfinished.

3.3 How to Reproduce

Toggle dark mode, then screenshot each screen. Compare the light and dark screenshots pixel‑by‑pixel; any region that retains the same RGB values as the light version is a hardcoded background.

3.4 How to Detect Automatically

3.5 Fix

Replace literal colors with semantic colors:

3.6 Prevent

Create a lint rule that flags any occurrence of #FFFFFF, #000000, or rgb(255,255,255) in UI files. Pair it with a pre‑commit hook that runs the rule on staged files.

---

4. Bug Pattern #3: Image Assets Not Adapted

4.1 Why It Happens

Logos, illustrations, or icons authored with a light background are exported as‑is and used in both themes. When placed over a dark surface, the image’s white parts disappear or the dark outlines blend into the background.

4.2 What the User Sees

A brand logo that looks like a faint ghost, or a decorative illustration that loses detail, making the UI feel low‑effort.

4.3 How to Reproduce

Enable dark mode, navigate to each screen that contains raster images, and visually inspect for loss of contrast. For a more systematic check, overlay a 50% gray layer and see if the image’s silhouette remains discernible.

4.4 How to Detect Automatically

4.5 Fix

Supply dark‑mode variants (either a separate file or a tint‑adjusted version) and reference them via the appropriate mechanism. For SVGs, prefer currentColor so the icon inherits the text color.

4.6 Prevent

Add a design‑system rule: “All raster icons must have a dark‑mode counterpart or be tintable.” Enforce it with an automated script that scans the asset folder for images lacking a -night suffix or a tintable flag.

---

5. Bug Pattern #4: System UI Overrides Not Respected

5.1 Why It Happens

Apps sometimes force a status bar or navigation bar color (Window.setStatusBarColor(Color.WHITE)) or hide the system UI (View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR). When the system is in dark mode, a forced light status bar makes icons invisible, and a forced dark status bar on a light app creates a visual clash.

5.2 What the User Sees

White status‑bar icons on a white background (invisible), or black icons on a dark background (also invisible). The same issue can appear with navigation gestures or the home indicator on iOS.

5.3 How to Reproduce

Toggle dark mode, then pull down the notification shade or look at the battery indicator. If the icons disappear or blend, you have a forced override.

5.4 How to Detect Automatically

5.5 Fix

Remove hard overrides and let the system decide, or set the style conditionally:


if (isSystemInDarkMode()) {
    window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
} else {
    window.decorView.systemUiVisibility = 0
}

5.6 Prevent

Add a rule to your style‑checker that bans direct calls to Window.setStatusBarColor or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR unless wrapped in a theme‑aware helper.

---

6. Bug Pattern #5: Dynamic Theme Switching Flicker

6.1 Why It Happens

When an app reads the theme only at launch and never listens for subsequent changes, toggling the system setting while the app is in the foreground causes a flash of the old theme before the UI updates.

6.2 What the User Sees

A brief white screen or a flash of light‑mode colors when switching to dark mode (or vice‑versa), which can be jarring and may trigger photosensitive discomfort.

6.3 How to Reproduce

  1. Launch the app in light mode.
  2. While the app is open, go to system settings and toggle dark mode.
  3. Observe the UI for any flicker or delayed update.

6.4 How to Detect Automatically

6.5 Fix

Subscribe to theme‑change events and update the UI immediately:


const mq = window.matchMedia('(prefers-color-scheme: dark)');
function handleChange(e) {
    document.documentElement.dataset.theme = e.matches ? 'dark' : 'light';
}
mq.addEventListener('change', handleChange);

6.6 Prevent

Enforce a code‑review guideline: “Any direct read of prefers-color-scheme or UiModeManager must be accompanied by a listener.” Add a custom ESLint/Android Lint rule that flags missing listeners.

---

7. Bug Pattern #6: Accessibility Labels and Announcements Mismatch

7.1 Why It Happens

Developers sometimes set static accessibility labels (contentDescription="Search" ) that reference a light‑mode icon (“magnifying glass”) but forget to update them when the icon changes to a dark‑mode variant with a different meaning (e.g., a filled vs. outline icon).

7.2 What the User Sees

TalkBack or VoiceOver reads a label that does not match the visual cue, causing confusion. For example, a dark‑mode “moon” icon may still be announced as “sun”.

7.3 How to Reproduce

Enable dark mode, turn on a screen reader, navigate to each control, and listen to the spoken label. Compare it to the visible icon or text.

7.4 How to Detect Automatically

7.5 Fix

Make accessibility labels theme‑aware, either by using string resources that change with the configuration or by computing the label at runtime based on the current theme token.

7.6 Prevent

Add a unit test that iterates over all UI elements with a contentDescription or aria-label and verifies that the label does not contain hardcoded light‑only terms like “sun”, “light”, or “bright” when the app is in dark mode.

---

8. Bug Pattern #7: Custom Controls Not Adapting

8.1 Why It Happens

Custom views (e.g., a segmented control, a range slider, or a chip group) often draw their own backgrounds and foregrounds using Canvas APIs. If the drawing code uses constant colors, the control will not reflect the theme.

8.2 What the User Sees

A slider thumb that stays dark on a dark track, making it impossible to see the current position, or a chip that remains light‑gray on a dark background, reducing tap target visibility.

8.3 How to Reproduce

Enable dark mode, interact with each custom control, and verify that all moving parts (thumb, fill, stroke) remain distinguishable.

8.4 How to Detect Automatically

8.5 Fix

Replace all hardcoded paint colors with values obtained from the theme:


val trackColor = ContextCompat.getColor(context, R.attr.colorSurface)
val thumbColor = ContextCompat.getColor(context, R.attr.colorPrimary)

8.6 Prevent

Enforce a rule that any Paint or CGColor initialization in custom view code must read from a theme resource. Provide a base class that throws an exception if a color literal is detected (using a simple regex scan during unit tests).

---

9. Bug Pattern #8: Print, Export, or PDF Generation Ignores Dark Mode

9.1 Why It Happens

Export features often reuse the same rendering pipeline as the on‑screen UI but forget to apply the theme before rasterizing. The result is a PDF or image that looks like the light‑mode version, even when the user requested dark mode.

9.2 What the User Sees

A exported report with a white background and dark text, making it hard to read when printed on dark paper or when the user prefers dark themes for accessibility.

9.3 How to Reproduce

  1. Switch to dark mode.
  2. Trigger the export/print flow (e.g., “Share → PDF”).
  3. Open the generated file and inspect the background color.

9.4 How to Detect Automatically

9.5 Fix

Before starting the export pass, explicitly set the theme context:


document.documentElement.dataset.theme = 'dark';
await exportToPDF();
document.documentElement.dataset.theme = '';

9.6 Prevent

Add a test that runs the export flow under both light and dark media queries and asserts that the output files differ in at least one pixel (using an image diff tool). Treat a lack of difference as a test failure.

---

10. Bug Pattern #9: Third‑Party Libraries or Themes Not Dark‑Mode Ready

10.1 Why It Happens

Teams integrate external SDKs (ads, analytics, maps, UI kits) that were built before dark mode existed or that only provide light‑mode resources. When the host app switches themes, the library’s UI sticks out.

10.2 What the User Sees

A bright white banner ad in the middle of a dark feed, or a map tile set that shows bright roads on a dark background, making the overall UI feel inconsistent.

10.3 How to Reproduce

Enable dark mode, navigate to any screen that hosts the third‑party component, and visually inspect for mismatched colors.

10.4 How to Detect Automatically

10.5 Fix

10.6 Prevent

Maintain an internal “dark‑mode compliance” spreadsheet for all third‑party dependencies. Add a CI step that fails if a new version of a dependency is introduced without a corresponding dark‑mode flag or if the library’s minSdkVersion/deploymentTarget predates dark‑mode support.

---

11. Bug Pattern #10: Animations and Transitions Cause Visual Glitches

11.1 Why It Happens

Developers sometimes animate properties like backgroundColor or opacity without defining both the light and dark end values. When the theme changes mid‑animation, the view may interpolate between an incorrect pair of colors, producing a brief flash of an unintended hue.

11.2 What the User Sees

A button that momentarily turns pink or teal while fading in, or a modal that shows a flashing stripe during its entrance animation.

11.3 How to Reproduce

  1. Enable dark mode.
  2. Trigger an animated transition (e.g., navigating to a new screen, opening a dialog).
  3. Use a high‑frame‑rate screen recording or the device’s “Show surface updates” developer option to spot color spikes.

11.4 How to Detect Automatically

11.5 Fix

Define animation values using theme tokens:


@keyframes fadeIn {
    from { opacity: 0; background: var(--color-surface); }
    to 1; background: var(--color-surface); }
}

11.6 Prevent

Add a lint rule that scans animation definition files (CSS @keyframes, Android Animator XML, iOS CABasicAnimation) for any hardcoded color values and flags them.

---

12. Test Matrix: Manual vs. Automated Detection Approaches

TechniqueWhat It CoversSetup EffortSpeed of FeedbackFalse‑Positive RateBest For
Manual visual inspection (designer/QA)Subtle contrast, contextual glitches, third‑party mismatchesLow (just a device)Slow (per‑release)Low (human judgement)Exploratory, edge‑case, usability
Automated contrast scanners (axe, Lighthouse, ATF)Contrast ratios, missing tokensMedium (CI integration)Fast (seconds)Low‑Medium (depends on rule set)Every PR, regression
Visual regression testing (Percy, Chromatic, screenshot diff)Pixel‑level changes, asset swaps, layout shiftsMedium‑High (baseline maintenance)Fast (per‑test)Medium (baseline drift)UI components, themed screens
Theme‑change listeners unit testsDynamically switching themes, flicker preventionLow‑Medium (test code)Fast (unit test)LowCore architecture, navigation
Accessibility label validation (screen‑reader scripts)Mismatched labels, missing descriptionsLow‑MediumFastLowAccessibility compliance
Third‑party dark‑mode compliance checkExternal SDKs, ads, mapsLow (dependency metadata)Fast (build step)LowDependency management
Animation color‑value lintHardcoded colors in animationsLowFastLowMotion‑heavy UI

Takeaway: Combine fast, automated checks (contrast, lint, unit tests) with periodic visual regression and exploratory manual sessions. The matrix helps you allocate effort where it yields the highest defect‑detection return.

---

13. Persona‑Driven Autonomous Exploration: How It Surfaces Dark‑Mode Bugs

Scripted test suites follow predetermined paths; they often miss bugs that only appear after a specific sequence of user actions combined with a theme toggle. Autonomous QA agents—like the one powering SUSA—solve this by simulating real‑world personas that explore the app without a script.

13.1 How the Agent Works

  1. Persona Selection – The agent picks a behavior profile (e.g., “elderly user who enlarges text”, “impatient user who taps rapidly”, “power user who opens deep links from notifications”).
  2. Environment Configuration – Before each session, the agent randomizes system settings: dark/light mode, font size, contrast enhancement, and even simulated network latency.
  3. Exploration Loop – Using UI‑automation hooks (Espresso/XCUITest for native, Playwright/Puppeteer for web), the agent performs taps, scrolls, text entry, and back navigation, guided by a heuristic that favors unexplored screens and edge‑case gestures (long press, swipe‑away).
  4. Observation – After each action, the agent captures a screenshot, runs an accessibility scan, and checks for theme‑specific violations (contrast, hardcoded colors, missing dark assets).
  5. Learning – Screens marked as “dead ends” or “crash prone” are stored; future sessions avoid repeating fruitless paths and focus on novel areas.

13.2 Concrete Example: Finding a Hidden Contrast Bug

*Persona*: “Novice user who prefers large text and dark mode.”

*Flow*:

  1. Launch app → Settings → Accessibility → Larger Text → 200% → Back.
  2. Toggle system dark mode.
  3. Navigate to the “Profile” screen.
  4. Tap the “Edit avatar” button (which opens a bottom sheet).

During step 4, the agent’s contrast checker flagged the bottom sheet’s background (#FAFAFA) against the sheet’s text (#212121) as a 3.2:1 ratio—failing AA for large text. The bug only appeared because the bottom sheet’s theme was not updated when the user increased the font size; the larger text triggered a different layout pass that exposed the missing token.

A traditional test that launched the app directly in dark mode with default font size would never have seen this bottom sheet, let alone the contrast failure.

13.3 Benefits for Dark‑Mode Quality

While SUSA provides this capability out‑of‑the‑box, the principles can be adopted in‑house by coupling a UI‑test framework with a simple state‑exploration loop and a set of persona‑driven heuristics (e.g., bias toward taps on uncovered elements, preference for scrolling to the bottom of lists).

---

14. Fixing and Preventing Dark‑Mode Regression

A robust dark‑mode strategy lives at the intersection of design, development, and testing. Below are concrete practices you can adopt today.

14.1 Establish a Design‑Token Foundation

14.2 Automate Theme‑Switch Tests in CI

14.3 Integrate Visual Regression Baselines

14.4 Conduct Regular Persona‑Driven Exploratory Sessions

14.5 Document and Share Dark‑Mode Guidelines

---

15. Quick Checklist for Release

✅ ItemHow to Verify
All text meets 4.5:1 contrast (AA) in dark modeRun axe/Lighthouse with dark‑mode flag; fail if any violation
No hardcoded #FFFFFF or #000000 in UI filesLint rule search for literals; CI blocks on match
Every image has a dark‑mode variant or is tintableScript that checks asset folder for -night suffix or tintable flag
Custom controls use theme tokens for paints/colorsUnit test that instantiates each control in both modes and asserts color source
Status bar / navigation bar respects system themeInstrumentation test that reads window flags after theme toggle
Export/print output uses dark backgroundGenerate PDF/PNG in dark mode and assert background equals dark token
Third‑party components are either dark‑mode ready or wrapped in a light containerDependency metadata check + runtime view hierarchy audit
Animations define both light and dark keyframesLint that scans @keyframes, Animator, CABasicAnimation for hardcoded colors
Accessibility labels change with theme when icons changeScreen‑reader test that captures label before/after theme toggle
Persona‑driven agent reports zero new dark‑mode issues on latest buildRun autonomous exploration; review report for dark‑mode tickets

If any item fails, treat it as a blocker and resolve before promoting the build to production.

---

16. Closing Takeaways

Dark Mode

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