RevoraWhy UsProcessServicesPricingBlogContact
Design

Dark Mode Done Right: The Design Principles Most Teams Skip

Most dark mode is inverted colors, not a real theme. Here's the token system, contrast math, and elevation logic that makes it feel designed.

An overhead view of a person working at a desk lit by panel lights in a dark room

Dark mode stopped being a novelty a while ago. It's now a baseline expectation, the same way a mobile-friendly layout or a fast page load is. Users toggle it at the OS level and expect every app and site to follow along, and if your product only exists in light mode, or ships a dark mode that clearly wasn't part of the original design, people notice immediately.

The trouble is that most dark modes aren't designed at all. They're inverted. Someone takes the light theme, flips white backgrounds to black, flips dark text to white, and ships it. The result usually looks worse than the light version it came from: harsh white text vibrating against pure black, shadows that disappear entirely, images that look washed out, buttons that feel like they're floating in the wrong z-index. It reads as a brightness slider, not a second theme.

A dark UI that feels premium is a deliberately built second system, not a filter applied to the first one. It needs its own background values, its own contrast rules, its own logic for elevation, and its own accent colors tuned for a darker surface. That's genuinely more design work than a CSS invert, and it's exactly the gap between products that look like they retrofitted dark mode and ones that look like they designed for it from the start. This is core design system work, the kind we cover in UI/UX design engagements, and it's become a standard line item in how we scope new builds.

TL;DR: Skip true black backgrounds and use a near-black with a slight warm or cool tint instead. Contrast rules for dark mode aren't just light mode's rules reversed, pure white text on black actually causes visual fatigue, so use off-white with layered opacity for hierarchy. Build color tokens (background, surface, border, text, accent) that map differently per theme rather than hardcoding one palette and inverting it. Shadows stop reading on dark backgrounds, so elevation should come from lighter surface colors instead. Photography needs framing and brightness adjustments so it doesn't look pasted on. Saturated accent colors that work on white often look neon or harsh on near-black and need to be desaturated or lightened. And dark mode needs its own testing pass, not a five-second toggle check.

Why True Black Is Almost Always the Wrong Background

The instinct is to reach for #000000. It's the most literal reading of "dark mode," and on OLED screens it even saves a bit of battery. But pure black creates two problems that undermine everything built on top of it.

First, there's contrast fatigue. Very high contrast between a pure black background and near-white text can cause a visual vibration effect, sometimes called halation, especially with thinner font weights or smaller text sizes. It's technically legible but tiring to read for any length of time, which is the opposite of what a comfortable reading theme is supposed to deliver.

Second, and more practically, pure black gives you nowhere to go. Dark mode interfaces need to express layers: a background, a card sitting on that background, a modal sitting on the card. If your base is #000, every surface above it has to get lighter, and you quickly run out of visual room before you're anywhere near a color that reads as "elevated."

The fix is a near-black base instead of true black. Something in the range of #121212 to #1a1a1a is the common starting point, and from there you pick a slight temperature. A cooler, faintly blue-gray tone (something like #0d1117) tends to read as technical and precise, which is why it shows up in developer tools and dashboards. A warmer, faintly brown-black tone reads softer and more editorial. Neither is "correct." The point is that the choice should be intentional and consistent with the rest of your brand, not just whatever the invert filter produced.

Contrast Ratios Work Differently in Dark UI, Not Just Inverted

WCAG's contrast requirements don't change based on theme. Normal text still needs a 4.5:1 contrast ratio against its background, large text and UI components need at least 3:1. That part is the same in light and dark mode. What changes is how you should hit those numbers.

In light mode, near-black text on a white background comfortably clears every threshold, so teams rarely think about it. In dark mode, teams often assume the inverse relationship holds and use pure white (#FFFFFF) text on their near-black background. It technically passes contrast checkers, but it overshoots. Full-white text on a dark surface is often harsher than it needs to be, and it collapses your ability to express hierarchy, because everything reads at the same maximum brightness.

The better approach, and the one used in most mature design systems, is to keep your base text color slightly off-white and then build hierarchy through opacity layers instead of separate colors:

  • High-emphasis text: white at roughly 87% opacity
  • Medium-emphasis text: white at roughly 60% opacity
  • Disabled or placeholder text: white at roughly 38% opacity

Each tier still needs to be checked against your actual background color with a real contrast tool, not assumed, since opacity changes the effective contrast ratio depending on what's behind it. But this system gives you legible hierarchy without ever resorting to pure white, and it's a big part of why some dark interfaces feel calm while others feel like they're shouting.

Build a Color Token System That Supports Both Themes

The root cause of most bad dark modes is architectural, not aesthetic. If your codebase has colors hardcoded as literal hex values scattered through components, there is no clean way to support a second theme. Someone eventually writes a global invert rule or a giant CSS override file, and that's how you end up with a theme that looks bolted on, because it was.

The fix is a semantic token system. Instead of a button referencing a hex value directly, it references a token, something like surface-primary or text-on-surface or border-subtle. Each token then gets a different value defined per theme:

  • background: the page-level base color, different for light and dark
  • surface: cards, modals, and panels sitting above the background
  • border: dividers and outlines, usually far more subtle in dark mode
  • text: split into high, medium, and low emphasis tiers
  • accent: your brand or action color, retuned per theme

Once colors are tokenized, switching themes becomes a matter of swapping the token map, not rewriting components. It also forces the useful discipline of deciding, upfront, what every surface and every piece of text should look like in both themes, rather than discovering the gaps after a designer clicks the toggle for the first time. This is the same systems thinking behind most of the interface patterns showing up in current web design trends, where theme flexibility is treated as a baseline requirement rather than a bonus feature.

Shadows Stop Working, So Surfaces Need to Lighten Instead

Shadows are a light mode convention. A drop shadow implies a light source casting darkness beneath an elevated object, which only reads correctly against a light background. Put a black box-shadow under a card that's sitting on a near-black background, and the shadow simply disappears. There's no contrast for it to create.

This is one of the most common tells of an inverted dark mode: cards and modals that look flat and indistinguishable from the background because the only elevation cue that existed, the shadow, stopped functioning the moment the background went dark.

The standard fix, popularized by Material Design and now common across most serious design systems, is to represent elevation through lightness instead of shadow. Higher elevation surfaces get progressively lighter shades of your base background color. A card at a low elevation might be a few percent lighter than the page background. A modal sitting above that card is lighter still. The visual hierarchy that shadows used to communicate in light mode gets communicated through a lightness scale in dark mode instead.

Some systems keep a faint shadow as a secondary cue, since it can still add a subtle sense of depth even if it's not doing the primary work. But if lightness isn't handling elevation as the main mechanism, the interface will read as flat no matter how carefully the shadows are tuned.

Photography and Imagery Need Their Own Treatment

Images are often the part of dark mode that gets skipped entirely, and it shows. A bright, high-key photo that looked great in a light layout can feel like a hole punched through the interface when it's dropped onto a near-black background. The edges look harsh, the brightness feels out of step with everything around it, and it pulls the eye in a way that has nothing to do with the image's actual content.

A few practical adjustments make a real difference:

  • Add a subtle border, rounded corner, or padding so images read as framed content rather than a raw rectangle dropped onto the surface
  • Slightly reduce brightness or add a faint dark overlay on very light photography so it sits more comfortably against the surrounding UI
  • Treat icons and illustrations separately from photography. Icons often need a dedicated dark mode version, not just a color swap, since fine details and strokes that read fine on white can disappear or look muddy on dark backgrounds
  • Check any images with transparent backgrounds carefully. A logo or icon designed with dark text for a white background will effectively vanish in dark mode unless a second version exists

None of this is complicated, but it does require someone to actually look at every image type used across the product in the context of the dark theme, rather than assuming imagery will just carry over.

Accent Colors Need to Be Retuned, Not Reused

A saturated brand blue or a bright action color that looks confident on a white background often looks harsh, almost neon, on near-black. This is a basic property of how color perception works: the same saturated hue reads very differently depending on what surrounds it, and a dark surface tends to amplify saturation in a way that can feel aggressive rather than premium.

The common mistake is treating the accent color as fixed brand equity that has to stay pixel-identical across both themes. In practice, most solid dark mode systems adjust their accent color specifically for the dark theme, usually by slightly desaturating it, lightening it, or both. The color should still be clearly recognizable as the same brand color, just tuned so it sits comfortably on the darker surface instead of vibrating against it.

The same logic applies to semantic colors like error red, warning yellow, and success green. Each of these needs its own dark mode variant checked against the new background, not a direct carryover from the light theme's values. This is exactly the kind of detail that separates a genuinely designed dark theme from an inverted one, and it's a recurring focus in the systems work we do through UI/UX design projects.

Testing Dark Mode Means More Than Flipping a Toggle Once

A huge share of dark mode bugs never get caught because testing consists of clicking the toggle, looking at the homepage for a few seconds, and moving on. Real dark mode testing needs to cover the same ground as any other serious QA pass, just aimed at the second theme.

Worth checking specifically:

  • Every screen state, not just the happy path: empty states, loading states, error states, and form validation messages all need their own dark mode pass
  • Long and short content, since text truncation and wrapping can interact differently with the tighter contrast range in dark mode
  • Actual contrast tools run against real rendered colors, not assumptions carried over from the light theme's numbers
  • Different screen types, since OLED and LCD panels render near-black backgrounds differently, and what looks fine on one can look banded or gray on the other
  • Real device testing in a bright room, since dark UIs viewed in daylight behave differently than the same interface tested in a dim office

It's also worth using proper tooling rather than eyeballing it, whether that's contrast-checking browser extensions, design tool plugins, or automated accessibility scans built into your CI pipeline. We cover some of the tooling that actually earns a place in a modern workflow in the tools that matter, and contrast and accessibility checking is consistently one of the categories worth investing in properly rather than treating as optional.

Dark Mode Is a Design System Problem, Not a Feature Toggle

Every principle here points back to the same idea: dark mode done well is a parallel design system, built with its own background values, contrast logic, elevation model, image treatment, and accent tuning, connected to the light theme through shared tokens rather than a shared hex list. It takes more upfront work than an invert filter, and it's also the entire difference between a dark theme that feels like an afterthought and one that feels considered.

If you're planning a rebuild or a new product and want dark mode handled as part of the actual design system rather than bolted on at the end, that's core to how we approach UI/UX design work. You can see examples of that kind of systems thinking applied to real projects in our case studies, or get a sense of scope and cost for your own project with our project estimator.

How far up the dark-mode ladder should you climb?

Everything above argues for the top rung. It is worth knowing what the cheaper rungs actually buy you first. Read the "Where it breaks" column before anything else, then pick the lowest row whose failure mode you can live with for the next two years — that is your scope. Browser-support facts below are from MDN, caniuse and Chrome's developer documentation, checked 8 August 2026. Support percentages and Baseline statuses move, so look each feature up on MDN or caniuse before you use any number here to justify a decision.

Approach What you actually build Who controls the theme Platform support / mechanics Where it breaks Right call when
1. Ship nothing, let the browser darken it Nothing — or one line to opt out deliberately. Chrome's algorithm, on Android only, and only for users who have both OS dark mode on and Chrome's site-darkening option turned on. Experimental, Android-only. Chrome introduced Auto Dark Theme as an origin trial in Chrome 96 on Android. It has never been documented as a stable, universally-enabled feature, and Chrome's current Android help pages describe only browser-UI dark theme — so treat the reach as small and unpredictable, not as a plan. The opt-outs are ordinary CSS and are stable: color-scheme: only light on :root, or <meta name="color-scheme" content="only light">. An algorithm picks your colors. Brand accents, image treatment and contrast are all out of your hands, and you find out how it looks from a user's screenshot. Chrome's own docs recommend building a curated dark theme rather than opting out. You are staying light-only on purpose and want to say so explicitly rather than get auto-darkened by accident, or the site is being rebuilt within the quarter.
2. Global invert filter One CSS rule (filter: invert()), maybe a toggle. You, notionally. Support is not the constraint. filter is available everywhere that matters. The constraint is that inversion is content-blind. Inverts everything: photos, logos, video, charts, screenshots. Your brand color becomes its complement. This is exactly the "reads as a brightness slider, not a second theme" failure described above. Never in production. Useful only as a ten-minute smoke test to find which components have hardcoded hex values.
3. OS-following theme (media query + tokens, no toggle) A semantic token layer — background, surface, border, text, accent — with a second value set inside @media (prefers-color-scheme: dark), plus color-scheme: light dark on :root and the matching meta tag. The operating system. The site follows it and never argues. Safe to rely on. prefers-color-scheme is Baseline Widely available — across browsers since January 2020 (Chrome 76, Edge 79, Firefox 67, Safari 12.1, iOS Safari 13), roughly 96% of global users on caniuse in August 2026.

color-scheme is Baseline Widely available since January 2022, and it is what makes scrollbars, form controls and the default canvas follow the theme.

Put it in the <meta name="color-scheme"> tag as well as CSS: MDN advises placing the meta tag before any style information to prevent unwanted flashes during load, because the browser can pick the right canvas immediately instead of waiting for the stylesheet.
No per-site override. Someone who keeps their laptop in light mode can never see your dark theme, and vice versa. Native form controls and scrollbars stay light if you skip color-scheme. Content sites, docs, blogs, marketing — most of the benefit for a fraction of the work, with no persistence or state-sync to maintain.
4. Tokens plus a real user toggle Everything in row 3, plus a persisted preference (typically data-theme on :root), three states — light, dark, follow system — and a small render-blocking inline script that applies the stored choice before first paint. The user, per site, with the OS as the default. Mostly, but not universally, safe. If you author with the terse light-dark() function, it is Baseline Newly available since May 2024 (Chrome 123, Edge 123, Firefox 120, Safari 17.5, iOS Safari 17.5) — close to 89% of global users on caniuse in August 2026, so older devices still need a fallback of two custom-property blocks.

MDN is explicit that light-dark() only works when color-scheme is set to light dark — so a manual toggle has to change color-scheme too, not just swap a class.
Flash of the wrong theme if the stored preference is read after first paint. The toggle also drifts silently out of step with the OS unless you keep an explicit "system" option instead of a two-way switch. Products people sit inside for hours, or anywhere dark mode is a stated, marketed feature.
5. Parallel dark design system (what this post argues for) Everything in row 4, plus the design work: an elevation-by-lightness surface ramp replacing shadows, retuned accent and semantic colors, dark variants for logos, icons and transparent PNGs, image framing and brightness treatment, and a dark pass over every state — empty, loading, error, validation. Same as row 4. Not a support question at all. No new browser capability is involved. This rung is entirely design and QA cost, which is why it is the one teams quietly skip. It rots. Unless tokens are the only sanctioned way to get a color, every new component reintroduces hardcoded hex and the dark theme decays release by release. You are already rebuilding, already standing up a design system, or the product is judged on screenshots — where the dark theme is the first impression.

Row 2 is wrong for everyone shipping to real users. Row 1 is wrong for any product with a strong brand color or heavy imagery, since an algorithm will reinterpret both. Row 3 is wrong for teams who plan to market dark mode as a feature, because there is nothing for a user to switch. Row 4 is wrong on its own for image-heavy or data-heavy interfaces, where a working toggle just reveals how much was never designed dark. And row 5 is wrong if nobody owns the token layer afterward — the maintenance, not the build, is what decides whether it survives.

Frequently Asked Questions

Is dark mode just the light theme with inverted colors?

No, and that's the most common mistake. A properly built dark theme uses its own background, surface, border, text, and accent values, tuned specifically for a darker environment, not a direct inversion of the light palette.

What background color should dark mode actually use instead of pure black?

A near-black in the range of roughly #121212 to #1a1a1a is the common baseline, often with a slight warm or cool tint depending on brand tone. Pure black (#000000) makes it harder to express elevation and can cause visual fatigue against bright text.

What contrast ratio does dark mode text need to meet?

The same WCAG thresholds apply regardless of theme: at least 4.5:1 for normal text and 3:1 for large text and UI components. What changes is the approach, using off-white text with layered opacity for hierarchy instead of pure white everywhere.

Do drop shadows still work in dark mode?

Not really. Shadows rely on contrast against a light background to read as depth, so they largely disappear on dark surfaces. Elevation in dark mode is typically shown by making higher surfaces progressively lighter, not by shadow alone.

How should a team test dark mode before shipping it?

Test every screen state (empty, loading, error, form validation), check real contrast ratios with proper tools, view it on both OLED and LCD displays, and check it in a bright room, not just a dim office. A single toggle check on the homepage misses nearly all the real issues.

Design

The Anatomy of a High-Converting App Onboarding Flow

SEO

Core Web Vitals in 2026: What Actually Moves Rankings

Business

Why Most Businesses Lose Leads (And How CRM Automation Fixes It)