Common Dark Mode Bugs and How to Catch Them
Common Dark Mode Bugs and How to Catch Them
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
- Web:
@media (prefers-color-scheme: dark) - Android:
values-night/resource folders,AppCompatDelegate.setDefaultNightMode() - iOS/tvOS:
UITraitCollection.userInterfaceStyle, asset catalogs with Appearance = Dark
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
- Enable dark mode at the OS level.
- 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).
- Note any element where the ratio falls below 4.5:1.
2.4 How to Detect Automatically
- Web – Run axe-core or Lighthouse with the
--presets=darkflag. - Android – Use the Accessibility Test Framework (ATF) with
isEnabledForDarkMode()checks. - iOS – Execute UI tests that query
UIColor.contrastRatio(with:)for each label.
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
- Web – Use a visual regression tool (e.g., Percy, Chromatic) with two viewport states: light and dark. Fail the build if any element’s computed
background-colordiffers from the token value. - Android – Write a unit test that inflates each layout in
Configuration.UI_MODE_NIGHT_YESand asserts that no view has a hardcoded#FFFFFFFFbackground unless explicitly allowed. - iOS – In XCTest, iterate over
UIView.subviewsand verifybackgroundColorequalsUIColor.systemBackgroundor a custom dark‑mode token.
3.5 Fix
Replace literal colors with semantic colors:
- Web:
background: var(--color-surface); - Android:
?attr/colorSurface - iOS:
UIColor.systemBackground
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
- Web – Use
pictureelements withmediaattributes that point to dark‑mode variants; run a test that asserts thesrcsetmatches the currentprefers-color-scheme. - Android – Use vector drawables with
android:tint="?attr/colorOnSurface"or providedrawable-nightfolders. A unit test can load each drawable in both modes and verify that the tint is applied. - iOS – Asset catalogs allow Appearance‑specific images; a UI test can check
UIImage.imageAsset?.register(in:)for each image name.
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
- Android – In an instrumentation test, read
Window.getAttributes().flagsandWindow.getDecorView().getSystemUiVisibility()after applyingUiModeManager.NIGHT_MODE_YES. Assert that the flags match the expected light/dark mode combination. - iOS – Check
UIViewController.prefersStatusBarStylereturns.darkContentor.lightContentappropriately. - Web – Not applicable; the browser UI is outside the page’s control.
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
- Launch the app in light mode.
- While the app is open, go to system settings and toggle dark mode.
- Observe the UI for any flicker or delayed update.
6.4 How to Detect Automatically
- Web – Listen for
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', handler)and assert that the handler runs within 100 ms of the media query change. - Android – Register an
UiModeManager.OnUiModeChangeListenerin an instrumentation test and measure the time between the callback and the first frame rendered with the new theme (usingFrameMetrics). - iOS – Observe
traitCollectionDidChange(_:)and useXCTWaiterto ensure the UI updates within the animation duration.
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
- Android – In an Espresso test, use
onView(withContentDescription(...))to capture the label and assert it contains the expected theme‑specific keyword. - iOS – Use XCTest to read
accessibilityLabeland compare against a dictionary of light/dark expected strings. - Web – Run axe-core with the
--rules=labelflag and verify that the computed label changes when theprefers-color-schememedia query toggles.
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
- Android – Create a UI Automator test that captures a screenshot of the control in both themes, then compute the perceptual difference (e.g., using SSIM). Fail if the difference is below a threshold for any inner element.
- iOS – Use
XCUIScreenshotand compare the control’s inner layers via Core Image filters. - Web – Use a visual regression tool that isolates the shadow DOM of the component and asserts that computed colors match the theme tokens.
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
- Switch to dark mode.
- Trigger the export/print flow (e.g., “Share → PDF”).
- Open the generated file and inspect the background color.
9.4 How to Detect Automatically
- Web – In a Puppeteer test, set
await page.emulateMediaFeatures([{ name: 'prefers-color-scheme', value: 'dark' }]), then call the export function and assert that the resulting PDF’s background color is#121212(or your dark token). - Android – Use
PrintDocumentAdapterand capture the printed page via a customPrintJobthat writes to aPdfDocument. Assert the page’s canvas background. - iOS – Use
UIPrintInteractionControllerand aUIPrintPageRenderersubclass to draw into aUIImageand verify the background.
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
- Web – Use axe-core to run on the iframe containing the third‑party content; flag any element with a computed
background-colorthat does not match:rootvariables. - Android – Launch the app with the library, then use
adb shell uiautomator dumpto inspect the view hierarchy for views whose background color is#FFFFFFFFwhile the parent is dark. - iOS – In a UI test, iterate over
window.subviewsand reject anyUIViewwhosebackgroundColorequals.whitewhenUITraitCollection.current.userInterfaceStyle == .dark.
10.5 Fix
- If the provider offers a dark‑mode version, switch to it via Gradle/Maven/CocoaPods flags.
- If not, wrap the component in a container that forces a light theme (
android:theme="@style/Theme.AppCompat.Light") or applies a color overlay/tint to neutralize the clash. - As a last resort, request the vendor to update their assets.
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
- Enable dark mode.
- Trigger an animated transition (e.g., navigating to a new screen, opening a dialog).
- 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
- Web – Use the Web Animations API to get the
effect.getComputedTiming()and assert that thebackgroundColorkeyframes contain both light and dark values. - Android – In an Espresso test, use
IdlingResourceto wait for the animation to end, then capture a series of frames viaMediaProjectionand verify that the color delta between consecutive frames stays below a perceptual threshold. - iOS – Use
XCUITestto add aXCUICoordinate‑based pixel‑color sampler during the animation’s duration.
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
| Technique | What It Covers | Setup Effort | Speed of Feedback | False‑Positive Rate | Best For |
|---|---|---|---|---|---|
| Manual visual inspection (designer/QA) | Subtle contrast, contextual glitches, third‑party mismatches | Low (just a device) | Slow (per‑release) | Low (human judgement) | Exploratory, edge‑case, usability |
| Automated contrast scanners (axe, Lighthouse, ATF) | Contrast ratios, missing tokens | Medium (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 shifts | Medium‑High (baseline maintenance) | Fast (per‑test) | Medium (baseline drift) | UI components, themed screens |
| Theme‑change listeners unit tests | Dynamically switching themes, flicker prevention | Low‑Medium (test code) | Fast (unit test) | Low | Core architecture, navigation |
| Accessibility label validation (screen‑reader scripts) | Mismatched labels, missing descriptions | Low‑Medium | Fast | Low | Accessibility compliance |
| Third‑party dark‑mode compliance check | External SDKs, ads, maps | Low (dependency metadata) | Fast (build step) | Low | Dependency management |
| Animation color‑value lint | Hardcoded colors in animations | Low | Fast | Low | Motion‑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
- 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”).
- Environment Configuration – Before each session, the agent randomizes system settings: dark/light mode, font size, contrast enhancement, and even simulated network latency.
- 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).
- 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).
- 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*:
- Launch app → Settings → Accessibility → Larger Text → 200% → Back.
- Toggle system dark mode.
- Navigate to the “Profile” screen.
- 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
- Coverage of Interaction‑Theme Combos – The agent naturally explores the cross‑product of UI states and theme states.
- Early Detection of Runtime‑Only Issues – Things like theme changes mid‑animation or after a network response are caught because the agent does not assume a static UI.
- Reduced Maintenance – No need to maintain a massive matrix of “theme × screen × gesture” test cases; the agent generates them on the fly.
- Feedback Loop – Each run improves the agent’s model of the app, making future passes more efficient at finding regressions.
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
- Define all colors, elevations, and spacing as tokens in a single source (e.g., a JSON file,
styles.xml, or CSS custom properties). - Ensure each token has both a light and a dark value that passes WCAG contrast.
- Enforce token usage via lint: any direct hex or rgb value in UI files triggers a build error.
14.2 Automate Theme‑Switch Tests in CI
- Web – Run
npm run test:darkthat executes Playwright with two contexts:{ colorScheme: 'light' }and{ colorScheme: 'dark' }. - Android – Add an instrumentation test suite that sets
UiModeManager.NIGHT_MODE_YESandNObefore each test class, asserting no hardcoded light colors appear. - iOS – Use
xcodebuild testwith-destination 'platform=iOS Simulator,name=iPhone 14,OS=latest'and overrideUITraitCollection.userInterfaceStyleviaUIView.appearance().
14.3 Integrate Visual Regression Baselines
- Take screenshots of every critical screen in both themes on every commit.
- Use a tool like Percy to automatically accept new baselines only after explicit approval.
- Set a diff‑threshold of 0.5% to catch subtle shifts while ignoring anti‑aliasing noise.
14.4 Conduct Regular Persona‑Driven Exploratory Sessions
- Schedule a 30‑minute autonomous‑agent run on a staging build before each release candidate.
- Review the agent’s report for any new dark‑mode tickets and prioritize them alongside functional bugs.
14.5 Document and Share Dark‑Mode Guidelines
- Create a living markdown file that lists: token names, prohibited hardcoded values, required contrast ratios, and steps to test each component.
- Onboard new engineers with a short workshop that walks through a dark‑mode audit of a sample screen.
---
15. Quick Checklist for Release
| ✅ Item | How to Verify |
|---|---|
| All text meets 4.5:1 contrast (AA) in dark mode | Run axe/Lighthouse with dark‑mode flag; fail if any violation |
No hardcoded #FFFFFF or #000000 in UI files | Lint rule search for literals; CI blocks on match |
| Every image has a dark‑mode variant or is tintable | Script that checks asset folder for -night suffix or tintable flag |
| Custom controls use theme tokens for paints/colors | Unit test that instantiates each control in both modes and asserts color source |
| Status bar / navigation bar respects system theme | Instrumentation test that reads window flags after theme toggle |
| Export/print output uses dark background | Generate PDF/PNG in dark mode and assert background equals dark token |
| Third‑party components are either dark‑mode ready or wrapped in a light container | Dependency metadata check + runtime view hierarchy audit |
| Animations define both light and dark keyframes | Lint that scans @keyframes, Animator, CABasicAnimation for hardcoded colors |
| Accessibility labels change with theme when icons change | Screen‑reader test that captures label before/after theme toggle |
| Persona‑driven agent reports zero new dark‑mode issues on latest build | Run 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
- Dark‑mode bugs are not merely cosmetic; they affect readability, accessibility, and perceived quality.
- The majority of defects stem from bypassing the platform’s theming mechanism—hardcoded colors, missing assets, or ignored system callbacks.
- A layered testing strategy—fast automated contrast and lint checks, supplemented by visual regression, unit tests for theme‑change listeners, and periodic persona‑driven autonomous exploration—covers both static and runtime failures.
- Design tokens are the single source of truth; enforce their use through code reviews and linting to prevent regressions at the source.
- Finally, treat dark mode as a first‑class citizen in your definition of done: every feature, every third‑party
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