How to Test Dark Mode in Mobile and Web Apps (2026)

Dark mode is not a color swap. It is a parallel design system with its own contrast constraints, image treatments, and state combinations. Poorly implemented dark mode ships with broken contrast, invi

January 26, 2026 · 3 min read · How-To Guides

Dark mode is not a color swap. It is a parallel design system with its own contrast constraints, image treatments, and state combinations. Poorly implemented dark mode ships with broken contrast, invisible icons, and UI elements that disappear on dark backgrounds. Here is the test plan.

Why dark mode is harder than it looks

A fully dark-mode compliant app needs:

Miss any of these and users encounter unreadable or broken screens.

What to test

Theme switching

  1. Switch OS theme while app is in foreground — app updates immediately (or with a single animation), no flash of wrong-theme content
  2. Switch OS theme while app is backgrounded — app updates correctly on resume
  3. Cold start in dark mode — no "flash of light theme" during splash
  4. Manual override in app — user can force light/dark independent of system

Per-screen

  1. All text legible (WCAG 1.4.3 contrast still applies)
  2. All icons visible against the dark background
  3. Input field borders and focus states visible
  4. Disabled states distinguishable from enabled
  5. Dividers and borders still visible (often invisible if hardcoded to #FFFFFF with low opacity)
  6. Shadows replaced with elevated surface colors (Material 3 approach)

Images and media

  1. Photos and user-uploaded content unaltered (do not invert)
  2. Icons with transparent backgrounds inverted appropriately
  3. Logos — decide per logo whether to invert, replace with dark-variant, or leave
  4. Charts and data visualizations — axis colors and gridlines adjusted
  5. Video thumbnails framed visibly on dark background

States

  1. Error states red enough to be visible, not too bright on dark
  2. Success states green enough, not too saturated
  3. Warning states distinguishable from error (color alone is not enough)
  4. Informational states distinguishable from CTAs

Third-party and web content

  1. WebView content respects dark mode (color-scheme CSS media query)
  2. In-app browser inherits theme
  3. Social share cards previews match theme
  4. OAuth screens (Google, Apple) — inherit or at least coexist
  5. Payment sheets (Stripe, Razorpay) — theme inheritance or clear contrast

Edge cases

  1. Low battery mode does not force dark theme unexpectedly
  2. Accessibility display filters (color inversion) combined with dark mode
  3. Night Light / Reading Mode on Android — app still legible
  4. True Tone on iOS — colors shift warmly in dark mode differently than light
  5. Emergency screens, lock screen widgets — theme respected
  6. Offline state banners — visible on both themes

Manual testing approach

  1. Install fresh on a device in light mode
  2. Tour the app end to end — note every color combination
  3. Switch to dark mode in OS settings
  4. Re-tour the app — watch for screens that flash light, text that fades, icons that vanish
  5. Switch back mid-flow — verify no state loss
  6. Force-close and reopen — both themes behave correctly

Do this twice: once on OLED (where true black is perfect) and once on LCD (where dark gray beats black for eye comfort).

Automated testing

Android (Compose or XML)


@Test
fun testDarkMode() {
    composeTestRule.setContent {
        AppTheme(darkTheme = true) { MyScreen() }
    }
    composeTestRule.onNodeWithText("Submit").assertIsDisplayed()
    // Assert contrast programmatically using AccessibilityChecks
}

Web (Playwright)


def test_dark_mode(page):
    page.emulate_media(color_scheme="dark")
    page.goto("https://myapp.com")
    # Assert background is dark
    bg = page.evaluate("getComputedStyle(document.body).backgroundColor")
    assert "rgb(15, 23, 42)" in bg  # your dark color
    # Run axe-core contrast check
    axe_results = page.evaluate("axe.run()")
    assert len([v for v in axe_results["violations"] if v["id"] == "color-contrast"]) == 0

iOS


func testDarkMode() {
    let app = XCUIApplication()
    app.launchArguments = ["-AppleInterfaceStyle", "Dark"]
    app.launch()
    // Assert visibility of key elements
    XCTAssertTrue(app.buttons["Submit"].isHittable)
}

How SUSA tests dark mode

SUSA runs explorations in both themes (configurable) and compares screen hashes. Discrepancies — new elements appearing only in one theme, missing elements, contrast violations — are flagged as findings. The accessibility_user persona runs contrast checks per screen in both themes. Visual regression diff across themes catches unintended differences.


susatest-agent test myapp.apk --theme light
susatest-agent test myapp.apk --theme dark
# Compare the two sessions
susatest-agent compare <session_light> <session_dark>

Common bugs

  1. Hardcoded white or black in drawables — looks fine in one theme, invisible in the other. Fix: use theme-aware color references.
  2. Third-party images over-bright in dark mode — glare. Fix: dim or wrap in a subtle overlay.
  3. Status bar color mismatch — app background dark, status bar still light. Fix: window.statusBarColor and isAppearanceLightStatusBars.
  4. PDF viewer assumes light mode — unreadable PDF on dark. Fix: force PDF viewer to use its own theme or provide a toggle.
  5. Custom illustrations designed for light — look alien on dark. Fix: commission a dark variant or replace with icons.

Test both themes every release. Make dark mode a first-class design system, not a post-hoc color inversion.

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