The Complete Guide to Dark Mode Design

How to design dark interfaces that are beautiful, accessible, and technically sound β€” from surface elevation to semantic tokens.
Abstract composition showing the duality of light and dark interfaces

Why Dark Mode?

More than aesthetics β€” and not as simple as inverting colors.

Dark mode isn't a trend β€” it's the default for most users now. But building one that works requires intentional design decisions, not a CSS filter.

82%

of smartphone users have dark mode enabled

2024 survey data

39–47%

battery savings on OLED at full brightness

Purdue University, 2021

33%

of US adults have astigmatism, affecting dark mode readability

WebAIM

Accessibility benefits are real β€” dark mode reduces total light for photosensitive users and eases eye strain in low-light environments. But it's NOT universally better: astigmatism causes light text on dark backgrounds to appear blurred, a phenomenon called halation. Offering both light and dark as a user choice matters.

OLED savings are real but nuanced β€” at typical 30–40% auto-brightness, savings are only 3–9%. The significant 39–47% figure only applies at 100% brightness. Worth designing for, but not the primary argument.

Surface & Elevation

Higher means lighter β€” the opposite of what shadows do in light mode.

In light mode, depth equals shadow β€” elements cast darker shapes below to feel raised. In dark mode, shadows are invisible against dark backgrounds. Instead, depth equals surface brightness: higher surfaces are lighter. This is how Material Design, Apple, and most modern dark themes create visual hierarchy.

Material Design recommends a base surface of #121212 β€” NOT pure black. Pure black (#000000) eliminates the range available for elevation levels and causes OLED ghosting artifacts during scrolling.

Diagram showing surface elevation levels from dp 0 to dp 24 with progressively lighter backgrounds
0dp0% overlay
1dp5% overlay
4dp9% overlay
8dp12% overlay
16dp15% overlay

Why Not Shadows?

Light Mode

Shadow creates depth βœ“

Dark Mode

Shadow disappears βœ—

On dark backgrounds, drop shadows blend into the surrounding darkness and lose their visual effect. Instead, use elevated surface colors combined with subtle borders at 5–10% white opacity to communicate depth and layering.

Color Adaptation

Why your brand blue needs a different shade in the dark.

Saturated colors vibrate and glow on dark backgrounds β€” an optical effect where color appears to pulse at its edges. This isn't just ugly; highly saturated hues on dark surfaces frequently fail WCAG contrast requirements. You can't simply reuse your light-mode palette and call it done.

The fix: desaturate by roughly 20 points and increase lightness by 10–15 points. Material Design uses the tonal palette 200–400 range for dark surfaces (versus 500–700 for light). Keeping the same hue family maintains brand recognition while ensuring readability and visual comfort.

Comparison showing saturated colors vibrating on dark backgrounds versus adapted versions

Light Mode

Dark Mode

β†’

H: 208Β° β†’ 208Β° Β |Β  S: 79% β†’ 59% (-20) Β |Β  L: 51% β†’ 66% (+15)

Light Background

Badge

Link text

Dark Background

Badge

Link text

How the Pros Do It

Slack

Restructured color naming from appearance-based tokens (@sk_black) to semantic ones (@sk_foreground). Stored CSS custom properties as comma-separated RGB values for dynamic opacity. Their component library handled roughly 90% of the theme switch automatically.

GitHub

Primer offers three dark themes: Dark Default, Dark Dimmed, and Dark High Contrast. The team created the Primer Prism tool for generating cohesive palettes and made hundreds of contrast improvements across more than 1,000 use cases.

Twitter / X

Originally shipped three themes β€” Default (white), Dim (#15202B, a dark blue-gray), and Lights Out (pure black for OLED savings). Dim was the deliberate middle ground: softer than pure black, easier on the eyes.

Typography & Contrast

Why pure white on pure black is technically accessible but practically unreadable.

Illustration of the halation effect where bright text glows against a dark background

Halation β€” bright text appears to glow and blur against dark backgrounds. Pure white (#FFFFFF) on pure black (#000000) yields a 21:1 contrast ratio, which exceeds WCAG AAA. But that maximum contrast creates visual discomfort and perceived blur, especially for the roughly 33% of adults who have some degree of astigmatism.

The practical sweet spot: a background of #121212–#1A1A1A paired with text of #E0E0E0–#F0F0F0 gives approximately 13:1–15:1 contrast. That's well above WCAG AA (4.5:1 for normal text) while avoiding halation. There is no β€œtoo much contrast” rule in WCAG, but usability research shows reading comfort drops above roughly 15:1.

The quick brown fox jumps over the lazy dog. Reading long passages of text in dark mode should feel effortless β€” no squinting, no glowing edges, no visual fatigue after minutes of sustained reading. Comfortable contrast is the goal.

Maximum contrastComfortable

Contrast

21.0:1

WCAG

AAA

Background

Text

Material Design Text Emphasis Levels

High emphasis β€” Primary text and headings

(87%)

Medium emphasis β€” Secondary text

(60%)

Disabled β€” Inactive labels and hints

(38%)

Font Weight Shifts in Dark Mode

Text appears optically bolder on dark backgrounds because light disperses outward against surrounding darkness β€” the same halation effect at a smaller scale. Body text set at weight 400 in light mode may need to remain at 400 or even drop to 300 in dark mode to maintain the same perceived weight. Medium weight (500) can read like semibold (600). If your dark theme feels β€œheavy,” try reducing font weights by one step before adjusting sizes.

Semantic Token Mapping

One component, two themes β€” the architecture that makes it work.

The key insight: components reference semantic tokens, which resolve to different primitive values per theme. The component layer never knows which theme is active. Theme switching only requires redefining semantic-to-primitive mappings.

Primitive

gray-900:

blue-300:

Semantic

dark theme

bg-primary β†’ gray-900

interactive β†’ blue-300

Component

card-bg β†’ bg-primary

button-bg β†’ interactive

CSS β€” Custom Properties
:root {
  --bg-primary: #ffffff;
  --bg-surface: #f5f5f5;
  --text-primary: #1a1a1a;
  --text-secondary: #6b7280;
  --interactive: #1e88e5;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg-primary: #121212;
    --bg-surface: #1e1e1e;
    --text-primary: #e0e0e0;
    --text-secondary: #9e9e9e;
    --interactive: #64b5f6;
  }
}

Images, Icons & Shadows

Adapting images, icons, and depth cues for dark backgrounds.

Three categories need adaptation β€” images (reduce brightness to avoid β€œlight-bombing”), icons (use currentColor, prefer outlined style), and depth cues (shadows to borders and elevation).

Transparent background assets are the biggest pitfall. A dark logo on a transparent PNG disappears on dark backgrounds. SVGs with hard-coded fill colors won't adapt.

Comparison of shadow and border techniques for creating depth in dark mode

Card Heading

First line of body text for this demo card.

Second line shows depth via shadow.

Light mode: shadow works

Card Heading

First line of body text for this demo card.

Shadow is invisible against dark.

Dark mode: shadow invisible

Card Heading

First line of body text for this demo card.

Subtle border defines the edge.

Elevated surface + border

Card Heading

First line of body text for this demo card.

Inner glow adds soft definition.

Elevated surface + inner glow

Do's and Don'ts

  • Provide alternate logo versions for dark mode
  • Use filter: brightness(0.8) contrast(1.1) on photographic images
  • Use currentColor in SVG icons for automatic adaptation
  • Assume transparent PNGs work on any background
  • Use bright/white shadows as replacement (looks like glowing artifacts)
  • Forget screenshots and illustrations with assumed-white backgrounds

Implementation Patterns

Five approaches, from pure CSS to full JavaScript control.

Code editor showing dark mode implementation patterns

From declarative CSS to programmatic JavaScript, each approach has tradeoffs. Most production apps combine several β€” a CSS media query for the initial load, a JS toggle for user override, and a cookie for SSR persistence.

CSS β€” prefers-color-scheme
/* Detects OS-level preference β€” no JS needed */
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #121212;
    --text: #e0e0e0;
    --accent: #64b5f6;
  }
}

body {
  background: var(--bg, #ffffff);
  color: var(--text, #1a1a1a);
}

Avoiding Flash of Wrong Theme (FOUC)

The most common dark-mode bug: the page loads in light mode, then flashes to dark after JavaScript runs. The fix is a tiny blocking script in the document head.

Inline script β€” place in <head> before any CSS
<!-- Place in <head> before any CSS -->
<script>
  (function() {
    var theme = localStorage.getItem('theme');
    if (!theme) {
      theme = matchMedia('(prefers-color-scheme: dark)').matches
        ? 'dark' : 'light';
    }
    document.documentElement.classList.add(theme);
  })();
</script>

For SSR frameworks (Next.js, Nuxt), use cookies instead of localStorage β€” read server-side and inject the class on <html> before hydration.

Common Mistakes

The eight most common dark mode failures β€” and how to fix each one.

Dark mode seems simple until you test it. These are the mistakes that show up in almost every first attempt β€” and each one degrades the experience in a different way.

Pure black backgrounds

βœ—Using #000000 β€” causes halation, feels like a void, OLED ghosting on scroll.

βœ“Use #121212 to #1A1A1A β€” provides depth range for elevation.

#000000
#121212
Oversaturated colors

βœ—Reusing light-mode accent colors β€” they vibrate and glow on dark backgrounds.

βœ“Desaturate ~20 points and increase lightness for comfortable contrast.

#1E88E5
#64B5F6
Forgetting disabled states

βœ—Light-mode disabled styles become invisible on dark backgrounds.

βœ“Audit every interactive state β€” hover, focus, disabled, placeholder β€” in dark mode.

Inconsistent elevation

βœ—Some cards elevated (lighter), others flat β€” confusing visual hierarchy.

βœ“Apply a systematic elevation scale across all surfaces.

Ignoring system preference

βœ—No auto-detection β€” forcing users to manually toggle.

βœ“Implement prefers-color-scheme detection + matchMedia change listener.

Programmatic inversion

βœ—Using filter: invert(1) β€” destroys images, shifts brand colors, loses semantic meaning.

βœ“Design dark mode intentionally with adapted colors, not CSS filters.

Transparent background assets

βœ—Dark logos on transparent PNGs disappear on dark backgrounds.

βœ“Provide alternate logo/icon versions or use CSS filters selectively on SVGs.

Same font weight both themes

βœ—Text appears bolder in dark mode due to light dispersal against dark.

βœ“Audit font weights β€” body at 400 may look like 500; consider reducing by one step.

Testing Checklist

A systematic checklist for shipping dark mode with confidence.

Don't ship dark mode without testing it systematically. OLED and LCD render differently, DevTools emulation misses edge cases, and automated tools only catch contrast ratios β€” not aesthetic issues.

0 of 22 complete

Contrast

Interactive States

Assets

Behavior

Edge Cases

Glossary

Key terms used throughout this guide.

TermDefinitionSection
Color-schemeCSS property that tells the browser which themes a page supports, adapting native UI elements.Β§7
ElevationVisual depth layer. In dark mode, higher elevation = lighter surface color.Β§2
FOUCFlash of Unstyled Content β€” the brief flash of wrong theme before JS applies the correct one.Β§7
HalationOptical effect where bright text glows/blurs against dark backgrounds, especially with astigmatism.Β§4
light-dark()CSS function that returns one of two values based on the computed color-scheme.Β§7
matchMediaJavaScript API for programmatically detecting media features like prefers-color-scheme.Β§7
OLEDDisplay technology where pixels emit their own light. True black (#000) pixels are fully off, saving power.Β§1
prefers-color-schemeCSS media feature that detects the user's OS-level light/dark preference.Β§7
Semantic tokenA design token that describes purpose (bg-primary) rather than value (gray-900).Β§5
SurfaceA background plane in the UI. Dark mode surfaces use lighter grays for higher elevation.Β§2
Tonal paletteA range of lightness steps for a single hue, used to pick light/dark variants.Β§3
VibrancyApple's system effect that blends content with what's behind it for depth.Β§2
WCAGWeb Content Accessibility Guidelines β€” defines minimum contrast ratios for text and UI elements.Β§4

Sources

References, research, and recommended reading.

Research & Data

  • Purdue University β€” OLED battery savings (ACM MobiSys 2021)

    Quantified dark mode power savings at various brightness levels

  • WebAIM β€” Dark mode accessibility recommendations (2025)

    Guidance on astigmatism, contrast, and offering user choice

  • Nielsen Norman Group β€” Dark mode user preference data (2024)

    Survey data on adoption rates across platforms

Design Systems

  • Material Design 3 β€” Dark Theme

    Elevation overlays, tonal surfaces, color adaptation guidelines

  • Apple Human Interface Guidelines β€” Dark Mode

    Vibrancy, materials, semantic background levels

  • Slack Engineering β€” Building Dark Mode on Desktop

    Semantic token restructuring, CSS variable strategy

  • GitHub β€” Primer color system and inclusive design

    Multiple dark themes, Primer Prism tool, 1000+ use case audit

Implementation References