Dark mode: designing it properly instead of inverting colors
Dark mode requests come in often, and the fastest wrong answer is a CSS filter that inverts the whole page's colors. It technically produces a dark background, and it also inverts photos into unusable negatives, breaks any color with specific brand meaning (a red error state becoming a strange inverted hue), and generally looks like exactly what it is: a shortcut, not a design.
Dark mode needs its own color palette, not an inversion
A proper dark mode defines its own set of background, surface, text, and accent colors, designed intentionally for dark backgrounds, not algorithmically derived from the light mode palette. Pure black backgrounds, for instance, often create harsher contrast with white text than a dark grey does, and brand accent colors frequently need a slightly adjusted, often desaturated, variant to avoid looking oversaturated against a dark background.
Elevation needs a different visual language
In light mode, elevation (a card sitting above the background) is usually communicated with drop shadows, which read poorly against a dark background where shadows have little visible contrast. Dark mode interfaces typically communicate elevation instead through subtly lighter surface colors for elements sitting "above" the base background, a genuinely different visual system, not just a shadow color swap.
Images and photos need their own treatment
Photography generally shouldn't invert or change between modes, but a full-bleed bright white image can feel jarring against a dark surrounding interface. We often apply a subtle brightness or overlay adjustment specifically to photographic content in dark mode, or add appropriate padding and framing so bright images don't create visual shock against the darker surrounding UI.
Testing both modes as first-class, not as an afterthought
We design and review both light and dark mode simultaneously, side by side, throughout the design process, not by designing light mode first and generating dark mode after the fact. Treating one mode as primary and the other as a derived afterthought is reliably how specific screens or edge-case states end up broken in whichever mode got less design attention.
System preference detection and manual override
We implement both a respect for the operating system's dark mode preference by default and a manual in-app toggle for users who want to override it, since some users want dark mode regardless of system setting, and others want their app to differ from an OS-wide dark preference. Building only one of these two paths is a common, easily avoided gap.
Contrast ratios don't automatically carry over between modes
A text and background pairing that comfortably passes accessibility contrast requirements in light mode doesn't automatically pass in dark mode with an inverted or adjusted palette, since contrast math depends on the specific colors involved, not just which one is lighter than the other. We run accessibility contrast checks separately for both palettes rather than assuming a pairing validated in one mode is safe in the other, since it's a common, easy-to-miss way a dark mode ships with real accessibility regressions despite looking fine on a quick visual pass.
A text and background pairing that comfortably passes accessibility contrast requirements in light mode doesn't automatically pass in dark mode with an inverted or adjusted palette, since contrast math depends on the specific colors involved, not just which one is lighter than the other.
Third-party embeds and widgets are where dark mode most often breaks
Payment widgets, chat plugins, embedded maps, and other third-party components frequently ship with their own fixed light-mode styling that a site's own dark mode CSS has no control over, creating a jarring bright rectangle in an otherwise dark interface. We audit every third-party embed specifically for dark mode compatibility during QA, checking whether the vendor offers a dark theme option, and when they don't, deciding deliberately whether to wrap the embed in a lightly padded light-mode-colored container rather than leaving an unstyled clash to ship unnoticed.
Charts and data visualizations need their own explicit dark mode design
Data visualizations built with a fixed color scale, particularly ones relying on white or very light backgrounds with thin gridlines, tend to look washed out or become genuinely hard to read once dropped into a dark mode context without deliberate adjustment. We treat chart color palettes as their own design decision requiring a distinct dark mode variant, adjusting gridline opacity, background contrast, and data-series colors specifically for legibility against a dark surface, rather than assuming a chart library's default theming will handle the transition acceptably on its own. This matters more than it might initially seem for any product with a dashboard or analytics view, since a chart nobody can comfortably read defeats the entire purpose of including it.
The engineering cost of doing this properly, and why it's worth budgeting for explicitly
Building a genuine dual-palette design system, rather than a single inverted stylesheet, adds real design and engineering time: every component needs review in both modes, every new feature added later needs the same dual consideration, and QA effectively doubles for any visual change. We quote dark mode as an explicit, separately scoped line item rather than folding it into a general design budget, specifically because clients who understand the actual time cost upfront tend to be far more satisfied with the result than clients who expected it to be a quick, nearly-free toggle and were surprised by how much real work a properly designed second palette actually requires.
A staged rollout approach for teams that can't afford the full cost upfront
For clients where a fully simultaneous dual-palette build isn't feasible within the current budget, we sometimes recommend a staged approach instead: shipping a genuinely well-designed dark mode for the highest-traffic, most-used screens first, while leaving lower-traffic screens on a simpler, less-polished fallback dark treatment temporarily, clearly documented as a known, intentional gap rather than an oversight. This lets a team ship real, meaningful dark mode support sooner without the false economy of a single naive inversion across the whole product, and it gives a clear, prioritized roadmap for extending full dark mode coverage to the remaining screens as budget allows in a later phase, rather than an all-or-nothing choice between full cost now or no dark mode at all.
Why we push back on "just add a dark mode toggle" as a scoping request
When a client's initial request is framed simply as "add a dark mode toggle," we take the time to walk through everything above before agreeing to a scope or timeline, since the phrase itself tends to understate the actual design and engineering commitment involved. Setting accurate expectations at this stage prevents a much worse conversation later, discovering mid-project that dark mode is taking meaningfully longer than either side originally assumed.