< Go back

Designing for Dark Mode: Principles, Pitfalls and a Production Checklist

30/5/2026 · 13 min read

Dark mode is not the absence of light. It is a completely different visual system — one that breaks most assumptions designers carry over from light mode without realising it.

Dark mode went from a power-user novelty to a mainstream expectation in roughly three years. The operating systems flipped the switch, the apps followed, and suddenly every design team had a second theme to maintain — whether they were ready for it or not. Most were not. The result is a generation of dark modes that look like the light mode held at gunpoint: the same hierarchy, the same colours, just dimmed to near- black and shipped before anyone asked whether it actually worked.

The dirty secret is that bad dark mode is easy to ship and hard to diagnose. It passes a glance test — it is dark, it renders, users accept it — but it quietly erodes the visual quality of everything it touches. Depth disappears. Hierarchy flattens. Text becomes either glaring or unreadable, and often alternates between both on the same screen. This article is about understanding why that happens and building the mental model you need to design dark mode that actually works, not just dark mode that exists.

Why Simply Inverting Colours Fails

The first instinct every designer has when asked to add dark mode is to invert the palette. White backgrounds become near-black, dark text becomes near-white, and coloured accents get tweaked until they stop burning the eyes. It takes an afternoon, it looks roughly correct in a screenshot, and it is almost entirely wrong.

The first problem is grey. Light mode uses grey as a subordinate — a step down from white that signals secondary information, disabled states, or subtle dividers. Invert that grey and you get a mid-tone that sits uncomfortably between the background and the foreground, reading as neither. Light mode grey communicates hierarchy because it is darker than the background; inverted dark mode grey communicates nothing because it is lighter than the background by roughly the same margin — but the human visual system does not experience lightness relationships symmetrically. What reads as "slightly subdued" in light mode reads as "slightly glowing" in dark mode, and the effect is visual noise, not hierarchy.

The second problem is shadow. Light mode uses drop shadows to simulate depth — a surface casts a shadow on whatever is behind it, and the shadow confirms the elevation. Invert the palette and the background is now dark, which means a dark shadow on a dark background is invisible. Teams usually respond by lightening the shadow, which produces a soft glow that looks more like a light source than a depth cue, and undermines the very hierarchy it was meant to reinforce. Shadow as a depth signal is a light-mode-native idea. In dark mode, it needs to be replaced, not preserved.

The third problem is brand colour. Saturated colours that work beautifully on white frequently look aggressive, fluorescent, or simply wrong on near-black. The luminance relationship between colour and background is completely different. A blue that communicates "primary action, confident, trustworthy" on a white background can communicate "neon, alarming" on a dark one. Inversion treats brand colours as neutral facts; they are not. Every colour in your palette needs to be reconsidered against the dark surface it will actually sit on.

The Semantic Token Approach

The correct architecture for dual-theme design is not a pair of colour palettes. It is a single layer of semantic tokens that sit between your design decisions and your raw colour values — tokens whose names describe intent rather than appearance. Not gray-900 and gray-50, but surface-background, surface-elevated, text-primary, text-secondary, border-default. The token names stay constant across both themes; only their resolved values change.

In practice this means defining your design token layer in CSS custom properties. A :root block sets the light-mode defaults; a [data-theme="dark"] or @media (prefers-color-scheme: dark) block overrides the same variables with their dark-mode equivalents. Every component in your design system references only the semantic tokens — never the raw hex values. When you switch themes, the component does not need to change at all. The token resolves to a different colour, and the component picks it up automatically.

The pay-off is not just cleanliness. The semantic token model forces you to make the design decision explicitly: what is this surface for, and what colour should that intent resolve to in each context? That discipline catches the inversions that look correct at a glance but break the hierarchy in ways you only notice after living with the interface for a week. It also makes maintenance tractable — when your brand refreshes its blue, you change one raw value in the palette layer, and every semantic token that references it updates automatically across both themes.

The mistake teams make most often is building semantic tokens for only the obvious cases — background, text, accent — and leaving everything else as hardcoded values. That works until the first edge case: a tooltip with its own background, a code block that needs a slightly different surface, a success state that uses a colour only expressible in the palette layer. Build the token vocabulary comprehensively from the start. The overhead is an extra hour in setup and a decade of sanity in maintenance.

Typography and Contrast in Dark Mode

Typography in dark mode is where the most teams stumble because the problems are subtle enough to miss in a design review but obvious enough to notice in extended use. The most common mistake is pure white text on a pure black background. It passes WCAG contrast checks with flying colours — a contrast ratio of 21:1, the theoretical maximum — and it is genuinely uncomfortable to read for more than a few sentences. The issue is halation: the bright white text appears to bleed into the dark background at the edges of each letterform, making the text look slightly blurry even when it is rendered at full sharpness. The fix is simple: use off-white, not white. Something in the range of #E8E8E8 to #F0F0F0 — luminance around 80–85% rather than 100% — eliminates halation while keeping the contrast ratio well above accessible thresholds.

Font weight is the second variable almost nobody adjusts. The same typeface at the same weight reads differently on dark and light backgrounds because of how the human visual system processes luminance contrast. On a light background a regular weight is crisp and appropriately substantial. On a dark background the same weight can read as thin and slightly ghostly, particularly at smaller sizes. The correction is to increase font weight by one step for body text in dark mode — regular becomes medium, medium becomes semibold — and to test the result on the actual device, not in a design tool preview that renders differently from a browser.

Secondary text is the third problem. In light mode, secondary text is typically achieved with reduced opacity or a lighter grey — the visual distance from primary text is easy to read and comfortably subtle. In dark mode, secondary text needs to be handled with precision. Too close to the background and it disappears; too close to primary text and it loses its secondary character. I use a three-step luminance scale: primary text at ~85% white, secondary text at ~55% white, and disabled text at ~30% white. Those numbers will shift depending on your background tone, but the principle — explicit luminance steps rather than opacity shortcuts — produces more predictable results across different rendering environments.

Elevation Without Shadows

In light mode, elevation is expressed through shadow. A card above the page surface casts a shadow downward; the shadow confirms the card is above the background. A modal casts a deeper shadow; the deeper shadow confirms it is above the card. The system is intuitive because it mimics physical light hitting physical surfaces — we have been reading it since childhood.

Dark mode breaks the shadow model because there is no plausible dark-room light source that would cast shadows visible against a near-black background. The solution, used consistently in Material Design 3 and increasingly everywhere else, is to express elevation through surface lightness rather than shadow darkness. Higher- elevation surfaces are lighter, not higher-shadowed. A base background sits at the darkest point in the scale; a card sits one step lighter; a modal sits two steps lighter; a tooltip or popover sits at the top of the scale. The tint progression replaces the shadow progression.

The tint values need to be subtle. The goal is a perceptible step, not a dramatic one — on a background of #121212, a card surface at #1E1E1E reads as elevated without announcing itself. Each step up should add roughly 5–8 luminance points; beyond that the surfaces start to look like distinct panels rather than an elevation stack. For products that need to communicate more than three or four elevation levels, you can combine the tint system with a very subtle border at the top edge of elevated surfaces — a 1px line at 10–15% white opacity mimics the specular highlight a light source would create at the upper edge of an elevated card, and reinforces the depth cue without introducing the glowing-shadow problem.

A common pitfall is applying the same coloured tint for brand-coloured elevated surfaces. If your design uses a blue card or a green panel, the elevation tint needs to be neutral white, not a lighter shade of the panel's colour — otherwise the tinting system conveys brand emphasis rather than elevation, and the two signals conflict. Elevation is structural information; keep its signal clean.

Images, Icons and Media

Photographs do not automatically adapt to dark mode, and in dark-mode contexts they often need a nudge. A bright, high-contrast image surrounded by a near-black interface can look like a lamp in a dark room — visually jarring and attention-hoarding in ways that break the intended content hierarchy. The standard correction is a subtle brightness reduction: dropping image brightness to 85–90% in dark mode softens the contrast with the surrounding interface without visibly degrading the image quality. In CSS this is a single line — filter: brightness(0.9) scoped to the dark theme — and it makes a disproportionate improvement to how the page reads as a whole.

Icons are a more delicate problem. Monochrome icons on a light background use dark fill; in dark mode you might invert to light fill, which generally works for purely geometric icons. The pitfall is icon sets with embedded optical adjustments — thin stroke icons whose visual weight was calibrated for a specific background tone. Inverting them directly often produces icons that read as too light and visually thin against a dark surface. The correct fix is to maintain separate dark-mode icon variants — slightly heavier stroke weight, adjusted to read at the same apparent weight as the light-mode versions despite the luminance difference. Most design systems skip this adjustment; most design systems have icons that look slightly off in dark mode without anyone being able to articulate why.

Video backgrounds, frequently used in hero sections, are the most aggressive problem. A bright video loop on a dark interface is almost always wrong — the contrast split creates an uncomfortable visual vibration that defeats both the video and the interface. Options in rough order of preference: source a version of the video graded for dark contexts; apply a dark overlay at 30–40% opacity on top of the video in dark mode; or simply disable the video background in dark mode and fall back to a static image with the same brightness treatment applied. The overlay approach is the fastest to implement but the least satisfying — it mutes the video for both modes equally rather than giving each mode a version that actually suits it.

Production Checklist

Before you ship a dark mode implementation, run through every item on this list on a real device in a dark room. The simulator lies. The design tool lies. The room with the lights on lies. A physical screen in low ambient light will show you everything the other environments hide.

  • Audit every hardcoded colour value in your codebase and replace it with a semantic token. Hardcoded values are the number one reason dark mode breaks in edge cases that testing missed.
  • Test pure white text on your darkest backgrounds and check for halation on high-pixel-density screens. If you see any blurring at letterform edges, step the text down to off-white.
  • Check font weight at every size in dark mode. Body text that reads correctly at 16px in light mode will often need to step up to medium weight in dark mode; small labels frequently need semibold.
  • Verify your elevation hierarchy using the tint system. Every elevated surface — cards, modals, tooltips, popovers, dropdowns — should sit on a visibly lighter surface than its parent. If two different elevation levels look identical, the hierarchy is broken.
  • Check all focus states. High-contrast focus rings designed for light mode frequently disappear against dark backgrounds. Your focus ring token needs a dark-mode value that maintains the same perceptual prominence, typically a lighter colour with increased opacity.
  • Run all images through the brightness filter check. Look at every photograph in context against the dark surrounding interface and ask whether the image looks integrated or like a foreign object on the page.
  • Test icon legibility at every size the icon appears in your product. Small icons at 16px that look fine in a design tool preview can read as visually underweight on a real screen in dark mode.
  • Check all status colours — success green, warning amber, error red, info blue — against your dark backgrounds. Saturated status colours from a light-mode palette frequently need to be desaturated and lightened for dark-mode contexts to read without aggression.
  • Verify the theme transition. If your product supports manual theme switching, test the transition itself — flash of wrong theme, incorrect flash of unstyled content, and transitions that are too fast or too slow all degrade the experience at the moment the user is most likely to notice.
  • Test with system-level dark mode enabled, not just your own toggle. Your CSS media query and your manual toggle may resolve differently depending on specificity order; always test both paths on every major browser and OS combination.
Next article

Designing Evals →

Working on a dark mode implementation?

Contact me