Dark Mode Design: What to Consider Before You Ship It
UI/UX Design

Dark Mode Design: What to Consider Before You Ship It

Aisha Rahman14 September 2025 14 min read

A client asked us last year to "just add a dark mode toggle" to their SaaS dashboard by the following sprint. Two weeks later the team had rewritten forty percent of the component library's color tokens, redrawn twelve icons, and re-tested every chart. Dark mode design has a reputation for being a quick CSS flip, and that reputation is wrong. Done properly it touches contrast ratios, elevation systems, image treatment, brand color behavior, and QA scope. Done as a quick flip, it ships with washed-out text, glowing pure-black backgrounds, and product photography sitting in ugly white boxes. If you are about to greenlight a dark theme, it is worth understanding what you are actually signing up for before a client or your own product roadmap locks in a date.

The demand for dark mode is real and worth taking seriously, but the reasons behind it are more mixed than most pitch decks admit. On OLED and AMOLED screens, a true black pixel draws almost no power, so dark interfaces do measurably extend battery life on phones with those panels, though the effect is much smaller on the LCD screens still common in cheaper Android devices and most laptops. Reduced eye strain in low-light environments is real for many users, particularly people who work at night or in dim rooms. But dark mode is not a universal accessibility win. Users with astigmatism often experience "halation," a blooming or glowing effect around light text on dark backgrounds that makes text harder to read, not easier. Some users with certain forms of light sensitivity actually prefer dark UI, while others with reading difficulties find light-on-dark harder to track line by line. Treat dark mode as a preference to support, not a fix that helps everyone equally.

The most common technical mistake is assuming you can invert your light theme and get a usable dark one. Contrast math does not work that way. WCAG 2.1 requires a contrast ratio of at least 4.5:1 for normal text against its background, and that ratio behaves asymmetrically between light and dark surfaces because of how the human eye perceives luminance. Pure white text (#FFFFFF) on pure black (#000000) technically scores a contrast ratio over 21:1, comfortably passing WCAG, but in practice it is uncomfortable to read for long stretches and produces the halation effect mentioned above. Most mature design systems avoid pure black entirely, using a near-black surface color instead, commonly somewhere in the range of #121212 to #1E1E1E, paired with an off-white text color around #E0E0E0 rather than full white. This single decision resolves a surprising share of the "dark mode feels harsh" feedback that comes back from usability testing.

Your brand colors will not survive the transition unchanged, and this is where a lot of stakeholders push back because they are attached to a specific hex value. Saturated, high-chroma colors that look crisp on a white background tend to vibrate or glow unpleasantly against dark surfaces, an effect graphic designers have described since the days of print work on black stock. A brand blue that reads as confident and professional on white can look like a highlighter on a dark dashboard. The fix is to build a second set of tonal values for dark surfaces, usually desaturating the color slightly and lifting its lightness value, rather than reusing the exact same hex code in both themes. This is also why serious dark mode work is done with a proper color token system rather than hardcoded hex values sprinkled through the codebase, because you need the ability to swap an entire palette based on theme context without touching component code.

Elevation and depth need an entirely different visual language in dark mode. In a light interface, elevation is usually communicated with drop shadows: a card sitting "above" the background gets a soft shadow beneath it, and the eye reads that as depth. Shadows barely register against a dark background because there is little luminance contrast for the eye to pick up. Material Design solved this years ago by using an overlay system instead, where higher-elevation surfaces get a slightly lighter tint layered on top of the base dark color rather than a shadow underneath. A card at elevation level 4 might sit at #2C2C2C while the base background sits at #121212, and that lightness step reads as "closer to the viewer" even without a visible shadow. If your dark theme still tries to communicate hierarchy purely with shadows, everything will look flat and the interface will lose the sense of layering that shadows provided in light mode.

Photography and illustration are the most commonly forgotten piece, and they are usually the reason a "finished" dark mode still looks unfinished in production. Product photos shot on white backgrounds, screenshots with white chrome, and diagrams with white canvases all become glaring rectangles sitting on top of a dark page, breaking the visual calm the theme was supposed to create. Fixing this properly means auditing every image asset in the product, not just the UI chrome. Options include re-exporting illustrations with transparent backgrounds so they sit naturally on either theme, adding a subtle rounded card treatment with padding around photography so the white edge reads as an intentional frame rather than a mistake, or in some cases maintaining two versions of an illustration set, one tuned for light backgrounds and one for dark. None of these are free, and this is usually the line item that blows up an otherwise reasonable dark mode budget estimate.

Data visualization needs its own pass, and teams that skip this step end up with charts that are technically dark-mode-compliant and practically unreadable. Category color palettes chosen for light backgrounds, especially ones using dark navy, black, or deep purple as distinct categories, lose their differentiation once the canvas itself goes dark. Gridlines that were a barely-there light grey on white need a different, barely-there treatment on dark, usually a low-opacity white rather than grey. Tooltips, axis labels, and legend text all need contrast re-verification independently of the base UI, because chart libraries frequently hardcode their own default text colors that never get touched during a theme migration and quietly fail contrast requirements in production.

Form inputs are another area where dark mode quietly breaks usability. Input borders that were a light grey outline on white background can nearly disappear against a dark surface if you do not deliberately lighten them, making it hard for users to tell where a text field starts and ends. Disabled states are a related problem: a common pattern in light mode is a light grey background to signal "disabled," but a light grey box on a dark background reads as a highlighted, active element rather than an inert one, which is the opposite of the intended signal. Focus states, error states, and placeholder text all need to be re-verified against the dark surface independently, because a red error color tuned for 4.5:1 contrast on white will often fail against a near-black background and needs its own lighter or more saturated variant.

QA scope effectively doubles once dark mode ships, and this is worth saying plainly to any client or stakeholder budgeting the work. Every screen, every state (empty, loading, error, success), and every responsive breakpoint now needs verification in both themes, not just a spot check on the homepage. The most common production bug we see after a dark mode launch is a hardcoded white background sitting inside a third-party embed, a legacy component, or an edge-case modal that nobody remembered to theme, producing a jarring white rectangle in an otherwise dark interface. Budgeting real QA time for this, rather than treating dark mode as a design deliverable that engineering just "handles," is the difference between a smooth launch and a week of hotfixes.

There is also a decision to make about how the theme is selected and remembered, and it affects both engineering scope and user experience. Respecting the operating system's `prefers-color-scheme` media query is the baseline expectation now for any modern web product, since both iOS and Android expose a system-wide dark mode setting that users increasingly expect apps to honor automatically. Beyond that baseline, most products still offer an explicit in-app toggle for users who want dark mode regardless of their system setting, or light mode even when their system is set to dark. That preference needs to persist, typically in local storage or a user profile setting, and needs to apply before the page paints to avoid a jarring "flash of wrong theme" on load, which is a real engineering task, not a CSS afterthought.

Not every product benefits equally from adding dark mode, and it is worth pushing back on requests that treat it as an unconditional feature. Developer tools, media and video apps, reading apps, and anything used for long sessions in low-light settings see genuine engagement lift from a well-built dark theme, and in some categories, like code editors and terminal-adjacent tools, users expect dark mode as the default rather than an option. Financial products, e-commerce checkout flows, and anything where trust and clarity are the primary design goals see far more mixed results, because a dark interface can read as less transparent or less "official" to some user segments, particularly older demographics less accustomed to dark UI conventions. Before committing engineering weeks to a dark theme, it is worth asking who actually asked for it and whether the answer is genuine users or a handful of vocal designers on the team.

Cost and timeline vary enormously depending on whether dark mode was considered at the start of a design system or bolted on afterward. Building a product from scratch with a token-based theming approach from day one adds relatively little overhead, typically ten to twenty percent on top of the base UI design and implementation timeline, because the second theme is largely a matter of defining a parallel token set rather than redesigning components. Retrofitting dark mode onto an existing product with hardcoded colors, inconsistent components, and years of accumulated one-off styles is a different project entirely, and agency quotes for that kind of retrofit on a mid-sized SaaS product commonly land between four and twelve weeks of combined design and engineering time, depending on how many screens and edge cases exist. Anyone quoting a dark mode retrofit in "a few days" either has a very small product or has not yet found all the hardcoded hex values buried in the codebase.

A recurring mistake worth calling out directly is reaching for a CSS filter, such as `filter: invert(1) hue-rotate(180deg)`, as a shortcut to a dark theme. This approach does technically darken a page, and some browser extensions use it as a blunt-force accessibility tool, but it inverts everything indiscriminately, including images, icons, and any colors that were already dark, producing bizarre results like inverted photographs and wrong-colored logos. It is a reasonable emergency accessibility patch for a page you do not control, and a poor foundation for a product's actual dark mode, because it gives you none of the control needed to fix the elevation, contrast, and image problems described above.

A practical way to scope this work, whether you are briefing an internal team or an outside agency, is to separate it into four workstreams: a token audit to identify every hardcoded color in the codebase and replace it with semantic tokens; a palette pass to define dark-optimized versions of every brand and semantic color, including chart and status colors; an asset audit to identify every image, icon, and illustration that needs a dark-compatible treatment; and a QA pass that explicitly doubles test coverage across both themes. Scoping it this way, rather than as a single vague "add dark mode" ticket, makes the actual size of the work visible early, which avoids the two-week estimate turning into a six-week scramble.

It is also worth deciding, before design work starts, whether dark mode is a nice-to-have secondary theme or a co-equal experience that gets tested and iterated on with the same rigor as the primary theme. Products that treat dark mode as an afterthought tend to have it drift out of sync over time, with new features shipping light-mode-only until someone files a bug. Products that treat both themes as first-class citizens, reviewing every new component in both modes before it ships, keep them in sync at a real but manageable ongoing cost. The second approach costs more per feature but avoids the much larger cost of a full dark mode audit and cleanup project eighteen months later, which is the position a lot of teams find themselves in once dark mode has quietly rotted.

Third-party embeds are a specific trap worth calling out on their own, because they sit outside your design system entirely and most teams do not think to audit them until a user reports the bug. Payment forms, live chat widgets, embedded video players, and analytics-driven pop-ups are frequently built by other companies with their own light-only default styling, and they will happily render a bright white box in the middle of your otherwise dark checkout flow. Some of these tools expose a theme or dark-mode configuration option, which is worth checking in the documentation before assuming you are stuck, while others require wrapping the embed in a styled container with padding and a border radius to visually contain the mismatch rather than fixing it outright. Either way, budget time specifically to click through every third-party integration in dark mode before launch, because these are exactly the surfaces that internal design reviews tend to skip.

On the implementation side, the technical approach that scales best is CSS custom properties (variables) mapped to semantic names rather than literal colors, for example `--color-surface` and `--color-on-surface` rather than `--gray-900` and `--white`, with the actual values swapped based on a `data-theme` attribute on the root element. This lets a component's styles reference `var(--color-surface)` once and automatically render correctly in both themes without any conditional logic inside the component itself. Frameworks like Tailwind CSS, common in a lot of modern web builds, support this pattern natively through its dark mode variant system, letting a class like `bg-white dark:bg-slate-900` express both states in one line, though teams with a more mature design system usually prefer defining the semantic tokens once in a central theme file rather than sprinkling `dark:` variants through every component, since the latter approach tends to drift out of sync as the codebase grows.

From our own experience running dark mode across a range of client dashboards, adoption is rarely a niche preference once both options are properly supported and easy to find. It is common to see somewhere between a third and half of logged-in users opt into dark mode when it is offered as an equal, easy-to-toggle choice, particularly in developer-facing tools, internal admin panels, and any product with above-average usage among engineers and designers. That adoption rate alone is usually enough to justify the investment for products in those categories, but it is worth measuring on your own product rather than assuming the number, since a consumer app aimed at a less technical, older demographic can see adoption well under ten percent, which changes the return-on-investment conversation considerably.

Tooling for verification is cheap and worth using deliberately rather than eyeballing contrast on a bright office monitor. Both major browsers now ship a device emulation mode that toggles `prefers-color-scheme` without changing your actual operating system setting, which is the fastest way to spot-check a page in dark mode during development. Standalone contrast checkers, several of them free browser extensions or simple web tools, let a designer paste in a foreground and background hex pair and get an immediate WCAG pass or fail rating for both AA and AAA thresholds, which is far faster than manually calculating luminance ratios and should be a standard step in any dark palette review rather than an occasional gut check. Running every semantic color pairing, text on surface, text on card, icon on button, through one of these checkers before a dark theme ships catches the majority of contrast failures before a single user ever sees them, and it takes an afternoon, not a specialist.

Finally, it is worth setting expectations with whoever is requesting the dark theme, whether that is a client, a product manager, or your own leadership, about what "done" actually looks like. A dark mode that only covers the primary marketing site or the top three dashboard screens, while leaving settings pages, empty states, and admin panels in a half-finished or purely inverted state, tends to generate more complaints than shipping no dark mode at all, because users notice the inconsistency more than they would have noticed the absence. Agreeing up front on the exact surface area covered, and being explicit about anything intentionally left in light mode for a later phase, avoids the awkward conversation after launch where a user reports a "broken" screen that was simply never in scope.

One last practical note for teams working with an outside agency or freelance designer on this: ask to see a dark mode audit deliverable, not just final mockups, before implementation starts. A short document listing every semantic token and its light and dark values, every image or icon needing a special treatment, and every third-party surface that needs checking, forces the scoping conversation to happen up front rather than being discovered screen by screen during development. It is a small amount of extra documentation overhead that consistently pays for itself in fewer surprises during QA, and it gives a client or internal stakeholder a concrete way to sign off on scope before a single hour of implementation time is spent, which avoids the awkward mid-project conversation about why the estimate has grown.

None of this is a reason to avoid dark mode. User demand for it is genuine, competitors increasingly ship it as standard, and for the right product category it measurably improves session length and user satisfaction. The point is that dark mode design is a real design and engineering discipline with its own rules around contrast, elevation, color behavior, and asset treatment, not a filter you apply the night before a release. Scope it honestly, budget the token and asset work alongside the visible UI work, and test it as thoroughly as the theme your users see by default, and it becomes a durable feature rather than a source of recurring bug reports.