Dark Mode Done Properly: Contrast, Colour and Comfort
Learn how to build dark modes that reduce eye strain and maintain accessibility without relying on pure black backgrounds or harsh white text.
Implementing a dark mode toggle is no longer an optional luxury; it is a baseline expectation for any modern application. However, the engineering reality of dark mode goes far beyond flipping a boolean flag and inverting your hex codes. Most developers encounter a common trap: they create a UI that is technically 'dark' but visually vibrating, difficult to read, or physically straining for the user. A proper dark mode is about managing luminance levels to preserve the user's night vision and reduce the 'halation' effect—the blurry glow that occurs when high-contrast white text sits on a pure black background.
At our studio, we approached this challenge during the development of Salah Companion, where users often check the app in the extremely low-light environment of a pre-dawn room. If the dark mode is poorly calibrated, the app becomes a flashbang. If it is too low-contrast, the text becomes a muddy mess. Achieving the balance requires a systematic approach to three pillars: depth, desaturation, and legibility.
The Fallacy of Pure Black
It is tempting to use #000000 (pure black) for dark mode backgrounds, especially with the prevalence of OLED screens that can turn pixels completely off to save battery. While this is efficient, it creates a significant usability problem known as black smearing. When you scroll quickly on an OLED screen, the transition from an 'off' pixel (pure black) to an 'on' pixel (any color) has a slight latency. This results in a distracting purple-ish smear behind moving elements.
Furthermore, pure black backgrounds create excessive contrast with white text. High contrast is generally good for accessibility—as we discussed in our guide to Android Accessibility Basics—but there is a point of diminishing returns. In a dark room, a contrast ratio of 21:1 (pure white on pure black) causes the light to bleed into the surrounding dark pixels on the human retina, making the text appear blurry or 'glowing.'
Instead, use a very dark grey as your foundation (e.g., #121212). This slight elevation reduces eye strain, eliminates smearing, and allows you to use shadows to communicate depth—which is impossible on a pure black surface.
Communicating Elevation Without Shadows
In light mode, we use drop shadows to show that a button or a card is sitting above the background. In dark mode, shadows are nearly invisible. To solve this, we use 'elevation overlays.' The higher an element is in the UI hierarchy, the lighter its background color should be. This mimics the physical world: objects closer to the light source appear brighter.
The Dark Mode Elevation Scale
| Elevation Level | Purpose | Recommended Gray Offset |
|---|---|---|
| 0dp | Main Background | Base (#121212) |
| 1dp | Content Cards | Base + 5% Lightness |
| 4dp | Dialogs & Modals | Base + 9% Lightness |
| 8dp | Floating Action Buttons | Base + 12% Lightness |
| 16dp | Context Menus | Base + 15% Lightness |
By following this scale, you create a sense of structure. When a user opens a stock verdict in ShariahSignal, the modal appears slightly lighter than the background, instinctively telling the user that the modal is a temporary layer on top of their main view. This is far more effective than trying to force a black shadow onto a dark grey background.
The Desaturation Rule
One of the most frequent mistakes in dark mode engineering is using the same brand colors used in the light theme. Vibrant, saturated colors (like a bright primary blue or a rich red) look great on a white background but appear to 'vibrate' or 'shimmer' against dark backgrounds. This makes the text incredibly difficult to focus on.
To fix this, you must desaturate your primary and secondary colors for the dark theme. High-saturation colors can also fail WCAG contrast requirements more easily on dark backgrounds because their perceived luminance is lower.
The Workflow for Color Adaptation:
- 1.Identify your light-mode primary: Let's say it's
#1A73E8(a saturated blue). - 2.Increase Lightness: Bring the color closer to white so it stands out against the dark.
- 3.Decrease Saturation: Lower the intensity so it doesn't 'vibrate.'
- 4.Test Contrast: Aim for at least 4.5:1 for body text and 3.1:1 for large UI components.
Typography and Readability Constraints
Dark mode flips the cognitive load of reading. Most people find dark text on a light background easier for long-form reading, while light text on a dark background is better for glanceable information. Because light text on a dark background appears visually 'bolder' due to the light bleed mentioned earlier, you should often decrease the font weight when switching to dark mode.
If you use a 'Medium' weight (500) for a headline in light mode, consider using 'Regular' (400) in dark mode. If you use a 'Regular' weight for body text, consider increasing the letter spacing (kerning) by 1% or 2%. This extra breathing room between characters prevents the light from merging the letters together in the user's eye.
Avoid using 100% white (#FFFFFF) for text. It is too harsh. Instead, use a high-opacity white. For example:
- Primary Text: White at 87% opacity.
- Secondary Text: White at 60% opacity.
- Disabled Text: White at 38% opacity.
This creates a natural hierarchy without requiring you to manage fifty different shades of grey in your CSS or XML files.
The Problem of 'Night Shift' and Blue Light
Many users enable system-level blue light filters (like 'Night Shift' or 'Eye Comfort Shield') at night. These filters shift the entire display toward the amber spectrum. If your dark mode already uses warm tones (like deep browns or oranges), these filters can make your app look muddy or make certain status colors (like green 'success' indicators) look indistinguishable from yellow 'warning' indicators.
When designing for apps like Mindful Assistant, which are specifically designed to be used during wind-down periods, we test against these system filters. Ensure that your functional colors—red for errors, green for success—are sufficiently different in hue so that even when shifted toward amber, the user can still tell them apart. This is a crucial part of functional accessibility that many teams overlook.
Handling Images and Media
Dark mode isn't just about the UI chrome; it’s about the content. A bright, high-key photograph can be jarring when the rest of the interface is dark. There are two engineering approaches to this:
- 1.The Image Overlay: Apply a dark, semi-transparent tint to images in dark mode until the user interacts with them. This reduces the overall luminance of the screen.
- 2.CSS Filters: For web-based apps, you can use
filter: brightness(.8) contrast(1.2);to subtly dim images and boost contrast, making them feel more integrated into a dark UI.
For icons, avoid using multi-colored illustrative icons that weren't designed for dark backgrounds. Stick to monochromatic vector icons that can easily change their tint based on the theme state. This ensures that a 'home' icon doesn't disappear just because its roof was hard-coded as a dark brown.
A Checklist for Production-Ready Dark Mode
Before you ship your dark mode update, run through this technical checklist to ensure you haven't introduced more friction than you've solved:
- [ ] Smear Test: Scroll through a list of items. If you see purple ghosting, your background is too close to pure black.
- [ ] The Squint Test: Squint at your UI. Can you still tell which elements are 'higher' (lighter) than others? If it looks flat, adjust your elevation overlays.
- [ ] The Vibrancy Test: Look at your primary action buttons. Do they feel like they are 'shimmering' or causing eye strain? If so, desaturate the color.
- [ ] Contrast Check: Run your dark mode hex codes through a contrast checker. Don't guess; use the numbers.
- [ ] Automatic Switching: Does your app respect the system-level 'Follow System' setting? Users hate having to manually toggle dark mode if they’ve already set a schedule at the OS level.
- [ ] Keyboard and Pickers: Ensure system components like the software keyboard, date pickers, and context menus are also invoking their dark variants.
What to do next
- 1.Audit your primary palette: Take your current brand colors and create a 'Dark' variant for each by reducing saturation by 10-20% and increasing lightness. Store these as separate theme variables.
- 2.Update your elevation system: Replace flat hex codes for 'surface' colors with a system that adds a semi-transparent white overlay to your base dark grey, increasing in opacity as elevation increases.
- 3.Test in the dark: Actually use your app in a pitch-black room for ten minutes. You will quickly notice which elements are too bright or which text segments are causing strain in a way that you would never notice in a bright office.