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
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:
- Every color defined in both themes
- Images adjusted or replaced for dark backgrounds
- Icons visible on the new surface
- Shadows re-thought (dark shadows on dark surfaces disappear)
- System UI (status bar, navigation bar) coordinated with the app theme
- Deep-link landings and WebViews respecting the theme
- Switch behavior correct when the OS theme changes mid-session
Miss any of these and users encounter unreadable or broken screens.
What to test
Theme switching
- Switch OS theme while app is in foreground — app updates immediately (or with a single animation), no flash of wrong-theme content
- Switch OS theme while app is backgrounded — app updates correctly on resume
- Cold start in dark mode — no "flash of light theme" during splash
- Manual override in app — user can force light/dark independent of system
Per-screen
- All text legible (WCAG 1.4.3 contrast still applies)
- All icons visible against the dark background
- Input field borders and focus states visible
- Disabled states distinguishable from enabled
- Dividers and borders still visible (often invisible if hardcoded to
#FFFFFFwith low opacity) - Shadows replaced with elevated surface colors (Material 3 approach)
Images and media
- Photos and user-uploaded content unaltered (do not invert)
- Icons with transparent backgrounds inverted appropriately
- Logos — decide per logo whether to invert, replace with dark-variant, or leave
- Charts and data visualizations — axis colors and gridlines adjusted
- Video thumbnails framed visibly on dark background
States
- Error states red enough to be visible, not too bright on dark
- Success states green enough, not too saturated
- Warning states distinguishable from error (color alone is not enough)
- Informational states distinguishable from CTAs
Third-party and web content
- WebView content respects dark mode (color-scheme CSS media query)
- In-app browser inherits theme
- Social share cards previews match theme
- OAuth screens (Google, Apple) — inherit or at least coexist
- Payment sheets (Stripe, Razorpay) — theme inheritance or clear contrast
Edge cases
- Low battery mode does not force dark theme unexpectedly
- Accessibility display filters (color inversion) combined with dark mode
- Night Light / Reading Mode on Android — app still legible
- True Tone on iOS — colors shift warmly in dark mode differently than light
- Emergency screens, lock screen widgets — theme respected
- Offline state banners — visible on both themes
Manual testing approach
- Install fresh on a device in light mode
- Tour the app end to end — note every color combination
- Switch to dark mode in OS settings
- Re-tour the app — watch for screens that flash light, text that fades, icons that vanish
- Switch back mid-flow — verify no state loss
- 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
- Hardcoded white or black in drawables — looks fine in one theme, invisible in the other. Fix: use theme-aware color references.
- Third-party images over-bright in dark mode — glare. Fix: dim or wrap in a subtle overlay.
- Status bar color mismatch — app background dark, status bar still light. Fix:
window.statusBarColorandisAppearanceLightStatusBars. - PDF viewer assumes light mode — unreadable PDF on dark. Fix: force PDF viewer to use its own theme or provide a toggle.
- 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