Why Dark Mode Matters
Dark mode has evolved from a niche preference to a mainstream expectation. In 2026, both iOS and Android enable dark mode by default during nighttime hours, and surveys show that over 80% of smartphone users keep it enabled at least part of the time.
Beyond user preference, dark mode has practical benefits. On OLED displays (which dominate modern smartphones), true black pixels are turned off, reducing power consumption by 30-60%. Dark mode also reduces eye strain in low-light environments. Both Apple and Google expect apps to support dark mode and factor it into quality assessments and featuring decisions.
How Dark Mode Works
iOS Appearance System
iOS uses a trait-based system. The current appearance is part of UITraitCollection, which flows through the entire view hierarchy. When the user toggles dark mode, UIKit triggers a re-render of all views.
Key concepts: system colors (UIColor.label, UIColor.systemBackground) adapt automatically. Asset catalogs support light and dark variants. Dynamic colors respond to appearance changes. SwiftUI handles it through system colors and the .preferredColorScheme modifier.
Android Theme System
Android uses the -night resource qualifier and DayNight theme. When the system switches modes, Android loads resources from -night directories. Material 3 provides automatic dark variants. Jetpack Compose uses MaterialTheme with darkColorScheme(). Force Dark exists for apps without native support but produces poor results.
Semantic Colors: The Right Way
The most common dark mode mistake is hardcoding color values. Use semantic roles instead:
| Semantic Role | Light Mode | Dark Mode | Usage |
|---|---|---|---|
| Background | White (#FFFFFF) | Near-black (#1C1C1E) | Screen backgrounds |
| Surface | Light gray (#F2F2F7) | Dark gray (#2C2C2E) | Cards, sheets |
| Primary text | Black (#000000) | White (#FFFFFF) | Main content |
| Secondary text | Dark gray (#3C3C43) | Light gray (#EBEBF5) | Captions, subtitles |
| Separator | Light gray (#C6C6C8) | Dark gray (#38383A) | Divider lines |
| Accent | Brand color | Lighter brand color | Interactive elements |
Pure black (#000000) backgrounds are generally avoided. Both Apple and Google recommend near-black or dark gray to maintain depth perception and reduce visual harshness. The exception is apps wanting true black for OLED power savings.
Image Handling
Images require special attention in dark mode. App icons and logos may need inverted variants because a dark logo on a transparent background disappears. Illustrations with light backgrounds look jarring, so provide dark variants or use transparent backgrounds. SF Symbols (iOS) and Material Icons automatically adapt in template rendering mode. Photos generally do not need modification.
Elevation and Shadows
Light mode uses shadows to create depth. Dark mode cannot use the same approach because shadows are invisible against dark backgrounds. iOS uses slightly lighter surface colors for elevated elements. Android M3 uses tonal elevation with a subtle primary color tint. In dark mode, lighter means higher (closer to the user).
Testing Dark Mode
Toggle between modes while the app is running (it should transition without a restart). Check every screen including error states, empty states, and loading states. Verify third-party components like ads, embedded web views, and maps. Ensure the status bar text updates correctly. Verify the launch screen supports both modes to prevent a white flash on dark mode startup. Test web views with separate dark mode CSS.
Letting Users Choose
Offer an in-app toggle (Light / Dark / System) because some users want a specific mode regardless of their system setting. In SwiftUI, use preferredColorScheme. In UIKit, override overrideUserInterfaceStyle on the root window. In Jetpack Compose, check the stored preference and wrap your theme accordingly.
Common Dark Mode Pitfalls
- Branded colors that look great on white but are unreadable on dark gray
- Hardcoded shadows that create weird outlines in dark mode
- Third-party SDKs without dark mode support creating visual inconsistency
- Always-white splash screen creating a bright flash before dark content loads
- Inverted text on inverted background resulting in invisible content