Design4 min read

Dark Mode Design

Design and implement dark mode - color pairing principles, elevation surfaces, CSS variable strategies, semantic tokens, and OLED-safe color choices.

Dark ModeColor TheoryCSS VariablesDesign TokensAccessibility

skill: Dark Mode Design category: Design tags: [Dark Mode, Color Theory, CSS Variables, Design Tokens, Accessibility] readingTime: 4

Dark mode done right is not just inverting colors. Done wrong, it's high-contrast eye strain. Done right, it reduces eye fatigue in dim environments, saves battery on OLED screens, and feels like a premium product touch. The key: design dark mode as a first-class theme, not an afterthought.

Workflow

  1. Choose dark palette foundationsPure black (#000000) backgrounds create harsh contrast with white text, causing halation (text appears to bleed). Dark-gray backgrounds with measured contrast ratios are more comfortable for extended reading.
  2. Design elevation with overlaysIn light mode, surfaces get darker as they elevate (shadows). In dark mode, shadows are invisible on dark backgrounds - elevation is communicated through lightness instead.
  3. Implement with CSS custom propertiesCSS custom properties (variables) enable instant theme switching without reloading. One set of semantic variables maps to light or dark values via data-theme attribute or media query.
  4. Add a theme toggleDetecting system preference is mandatory; a manual toggle is the user-centric enhancement. Users may prefer dark mode in a light-mode OS environment.
  5. Test dark mode accessibilityDark mode introduces unique accessibility issues: insufficient contrast, images with white backgrounds visible in dark mode, and focus rings invisible on dark surfaces.

Output Format

Dark mode: #0F1117 base with surface elevation via lightness, semantic CSS variables with data-theme attribute, system preference + manual toggle with FOUC prevention, and contrast ratio testing per WCAG AA.

:root { --bg: #FFFFFF; --surface-1: #F8FAFC; --text: #0F172A }
[data-theme="dark"] { --bg: #0F1117; --surface-1: #181B24; --text: #F1F5F9 }
Elevation: surface-1(5%) → surface-2(8%) → overlay(11%) white overlay
Toggle: localStorage > prefers-color-scheme > data-theme attr
FOUC fix: blocking <script> in <head> sets data-theme before render

Examples

Input

Output

Edge Cases & Gotchas

  • CSS variable inheritance and specificity - CSS variables inherit through the DOM. If a component sets its own background, the inherited text color may not have sufficient contrast. Always test dark mode in context (complete screens), not component-by-component.
  • Third-party iframes and dark mode - Embedded widgets (Stripe, Intercom, maps) don't receive your CSS variables - they have their own theming. Check if the third-party library has a dark mode option and configure it separately.
  • Print stylesheets - Dark mode media queries apply during printing, producing black-background printouts. Add @media print { :root { color-scheme: light; } } to force light mode for printing.
  • Video thumbnails on dark surfaces - Video thumbnails with white backgrounds become jarring in dark mode. Apply box-shadow: inset 0 0 0 1px rgba(255,255,255,0.05) to create a subtle outline without changing the thumbnail image.

When to Break These Rules

  • Skip dark mode for data-heavy enterprise tools with small, tech-savvy audiences where system preference is sufficient - don't build a manual toggle if your users are 95% in light mode.
  • Defer dark mode until v2 when your design system uses hardcoded color values throughout - retrofitting dark mode into a hardcoded system is 5× more expensive than building with semantic tokens from day one.

Reference Files

  • https://web.dev/prefers-color-scheme/
  • https://m3.material.io/styles/color/dark-theme

More skills in this library

Browse all technical skills - Mobile, AI/ML, Web, Backend, Design, and Product.