Designing for Dark Mode Without Breaking Light Mode
Dark mode and light mode can share one codebase. Learn how semantic color tokens, contrast checks, and depth cues keep both themes accessible and on-brand.

Your product looks sharp in light mode. Then a user flips their phone to dark mode at 11 PM, and your carefully chosen brand color turns into a glowing neon stripe, your "subtle" gray text disappears into the background, and a white card you forgot about flashes like a camera. Dark mode is not a filter you switch on at the end. It is a second visual system that has to coexist with the first, and getting one right while quietly breaking the other is one of the most common UI design mistakes we see.
The good news: dark mode and light mode can share the same components, the same layout, and the same code. What changes is how you think about color, contrast, and depth. Here is how to design for both without one undermining the other.
Why dark mode breaks light mode (and vice versa)
The root cause is almost always the same: colors hardcoded for one mode that get reused in the other. A designer picks #FFFFFF for a card and #111111 for text because it looks great on a light page. Six months later someone adds dark mode by inverting the background, and now that same black text sits on a dark surface and is unreadable.
Theming fails when color decisions are scattered. They live inside individual components, inline styles, one-off hex values, and screenshots handed to developers. There is no single place that says "this is what 'surface' means" or "this is what 'primary text' means." When that single source of truth is missing, dark mode becomes a patch instead of a system, and every new screen risks breaking one of the two themes.
Pure black and pure white are the second trap. A fully black background (#000000) with bright white text creates harsh contrast that causes halation, where text appears to vibrate, especially on OLED screens common across the GCC market. Pure white surfaces in dark mode feel like a flashbang. Both modes need softened extremes.
Build on semantic color tokens, not raw values
The single most important shift is moving from raw colors to semantic tokens. Instead of telling a button "be #0066FF," you tell it "use color-primary." Then you define what color-primary resolves to in each theme.
A practical token structure looks like this:
- Surfaces:
surface-base,surface-raised,surface-overlayfor backgrounds at different elevations - Content:
text-primary,text-secondary,text-disabledfor text hierarchy - Brand:
brand-primary,brand-hover,brand-muted - Feedback:
status-success,status-error,status-warning,status-info - Borders:
border-subtle,border-strong
Each token gets two values, one per theme. A component never knows which theme is active; it just asks for text-primary and gets the right color automatically. This is the core of maintainable theming, and it works the same whether you are building with CSS variables in a Next.js site, a ThemeData object in Flutter, or design tokens in Figma.
The payoff is enormous for teams. Designers change a token once and every screen updates. Developers stop guessing hex codes. New features inherit correct colors for both modes by default, which means dark mode stops being a separate project you constantly maintain.
Get contrast and depth right in both modes
Color in dark mode does not behave like color in light mode, and treating them identically is where accessibility quietly slips.
Soften your extremes. Use a very dark gray such as #121212 instead of pure black for backgrounds, and an off-white around #E8E8E8 instead of pure white for primary text. This reduces eye strain and the vibrating-text effect without losing the dark aesthetic.
Recheck every contrast ratio per theme. A color pair that passes WCAG AA in light mode can fail in dark mode and the reverse is also true. Body text should hit at least a 4.5:1 contrast ratio against its background, large text and UI components at least 3:1. Run the check for both themes, not once. This is non-negotiable for accessibility and it is increasingly a procurement requirement for government and enterprise clients in Saudi Arabia and the UAE.
Desaturate brand colors for dark mode. A vivid saturated blue that looks confident on a white page becomes painfully bright on a dark one. Create a slightly desaturated, sometimes lighter variant of each brand color for the dark theme so it reads as the same color without burning.
Rethink elevation. In light mode, shadows communicate depth. Shadows are nearly invisible on dark backgrounds, so dark mode uses lighter surface colors to show elevation instead. A modal sits on a lighter gray than the page behind it. Plan your surface tokens with this in mind from the start.
A workflow that keeps both themes honest
Process matters as much as palette. Teams that ship reliable theming follow a few habits.
- Design both modes in parallel, never sequentially. Sketch the dark variant of a screen in the same session as the light one. Problems surface immediately instead of months later.
- Use a shared token file as the contract between design and development. Figma variables map directly to CSS custom properties or Flutter theme values, so there is one vocabulary across the whole team.
- Test on real OLED and LCD devices, not just a simulator. Dark backgrounds render very differently on physical hardware, and contrast issues often only appear on a real screen in a dim room.
- Respect the system preference by default, then let users override it. Reading
prefers-color-schemeand offering an explicit toggle covers both the user who wants automatic switching and the one who has a firm preference. - Audit images, logos, and illustrations separately. Transparent PNGs with dark line art vanish on dark backgrounds. Provide theme-aware asset variants or add subtle containers.
Key takeaways
- Dark mode breaks light mode because colors are hardcoded per-screen instead of defined once. Semantic color tokens with a value per theme are the fix.
- Avoid pure black and pure white. Soften extremes to reduce eye strain and the vibrating-text effect, especially on OLED displays.
- Verify WCAG contrast ratios in both themes independently. A pair that passes in one mode can fail in the other.
- Use lighter surfaces, not shadows, to show depth in dark mode, and desaturate brand colors so they do not glare.
- Design and test both modes together from day one. Retrofitting dark mode later is where most theming bugs come from.
Dark mode is not a cosmetic add-on. It is a signal that your product respects how people actually use their devices, and a well-built theming system pays off every time you ship a new screen. At SummationWorks we build interfaces that stay accessible and on-brand in any mode, from Flutter apps to high-performance web products. Explore our services, see our work, or get in touch to talk through your product's design and theming.
About the author
SummationWorks
SummationWorks is a software development company building web apps, mobile apps, and AI tools for startups and growing businesses across the US, UK, and GCC.
More about usRelated Articles
designDesigning for Accessibility From Day One: A Practical Guide
Why building accessibility and WCAG compliance into design, code, and testing from the start is cheaper than retrofitting it later.
designBuilding a Brand Identity for Your Startup: A Practical Guide
A practical guide to building a startup brand identity that earns trust, from strategy and logo to colors, typography, and bilingual consistency.
designColor Theory for Digital Products: Building a Palette That Works
A practical guide to color theory for digital products: build a scalable, accessible, on-brand palette for real UI design.