Dark Mode Implementation Cheat Sheet – Complete Developer Guide

Introduction

Dark mode is a display setting that uses dark backgrounds with light text and UI elements, reducing eye strain in low-light environments and potentially saving battery life on OLED displays. With over 80% of users preferring dark interfaces, implementing dark mode has become essential for modern web and mobile applications. This cheatsheet provides practical guidance for implementing robust, accessible dark mode functionality across different platforms and frameworks.

Core Concepts & Principles

Color Theory Fundamentals

  • Contrast Ratios: Maintain WCAG AA compliance (4.5:1 for normal text, 3:1 for large text)
  • Color Temperature: Use warmer colors in dark mode to reduce blue light exposure
  • Semantic Colors: Define colors by purpose (primary, secondary, success, error) rather than specific values
  • Elevation System: Use lighter shades to indicate higher elevation/depth in dark themes

Design Principles

  • True Black vs Dark Gray: Use dark gray (#121212) instead of pure black (#000000) for better contrast
  • Reduced Opacity: Lower opacity for disabled states and secondary content
  • Brand Consistency: Maintain brand colors while adapting for dark contexts
  • Progressive Enhancement: Design light mode first, then adapt for dark mode

Implementation Approaches

1. CSS Custom Properties Method

:root {
  --bg-primary: #ffffff;
  --text-primary: #000000;
  --border-color: #e0e0e0;
}

[data-theme="dark"] {
  --bg-primary: #121212;
  --text-primary: #ffffff;
  --border-color: #333333;
}

2. CSS Media Query Method

@media (prefers-color-scheme: dark) {
  body {
    background-color: #121212;
    color: #ffffff;
  }
}

3. Class-Based Toggle Method

.light-theme {
  --bg: #ffffff;
  --text: #000000;
}

.dark-theme {
  --bg: #121212;
  --text: #ffffff;
}

Platform-Specific Techniques

Web Development

HTML Setup

<html data-theme="light">
<head>
  <meta name="color-scheme" content="light dark">
</head>

JavaScript Toggle

function toggleDarkMode() {
  const html = document.documentElement;
  const currentTheme = html.getAttribute('data-theme');
  const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
  
  html.setAttribute('data-theme', newTheme);
  localStorage.setItem('theme', newTheme);
}

System Preference Detection

const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
const storedTheme = localStorage.getItem('theme');

function setTheme(theme) {
  document.documentElement.setAttribute('data-theme', theme);
}

// Initialize theme
if (storedTheme) {
  setTheme(storedTheme);
} else if (prefersDark.matches) {
  setTheme('dark');
}

React Implementation

import { useState, useEffect } from 'react';

function useDarkMode() {
  const [isDark, setIsDark] = useState(false);
  
  useEffect(() => {
    const stored = localStorage.getItem('darkMode');
    const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
    setIsDark(stored ? JSON.parse(stored) : prefersDark);
  }, []);
  
  const toggle = () => {
    setIsDark(!isDark);
    localStorage.setItem('darkMode', JSON.stringify(!isDark));
  };
  
  return [isDark, toggle];
}

Vue.js Implementation

// Composable
export function useDarkMode() {
  const isDark = ref(false);
  
  const toggle = () => {
    isDark.value = !isDark.value;
    localStorage.setItem('theme', isDark.value ? 'dark' : 'light');
    document.documentElement.setAttribute('data-theme', isDark.value ? 'dark' : 'light');
  };
  
  onMounted(() => {
    const stored = localStorage.getItem('theme');
    const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
    isDark.value = stored ? stored === 'dark' : prefersDark;
  });
  
  return { isDark: readonly(isDark), toggle };
}

Color Palette Templates

Basic Dark Mode Palette

ElementLight ModeDark ModeUsage
Background Primary#FFFFFF#121212Main background
Background Secondary#F5F5F5#1E1E1ECards, modals
Text Primary#000000#FFFFFFMain content
Text Secondary#666666#AAAAAASubtitles, descriptions
Border#E0E0E0#333333Dividers, outlines
Accent#007AFF#0A84FFLinks, buttons

Material Design Dark Theme

ComponentLightDarkHex Dark
Surface#FFFFFFSurface#121212
Primary#6200EEPrimary#BB86FC
Secondary#03DAC6Secondary#03DAC6
Error#B00020Error#CF6679
On-Surface#000000On-Surface#FFFFFF

Framework-Specific Solutions

Tailwind CSS

// tailwind.config.js
module.exports = {
  darkMode: 'class', // or 'media'
  theme: {
    extend: {
      colors: {
        dark: {
          bg: '#121212',
          surface: '#1E1E1E',
          text: '#FFFFFF'
        }
      }
    }
  }
}

Usage:

<div class="bg-white dark:bg-dark-bg text-black dark:text-white">
  Content adapts to theme
</div>

Bootstrap Dark Mode

// Custom SCSS
$dark-bg: #121212;
$dark-text: #ffffff;

[data-bs-theme="dark"] {
  --bs-body-bg: #{$dark-bg};
  --bs-body-color: #{$dark-text};
}

Styled Components

const theme = {
  light: {
    bg: '#ffffff',
    text: '#000000'
  },
  dark: {
    bg: '#121212',
    text: '#ffffff'
  }
};

const Container = styled.div`
  background: ${props => props.theme.bg};
  color: ${props => props.theme.text};
`;

Common Challenges & Solutions

Challenge 1: Flickering on Page Load

Problem: Theme loads after initial render causing flash Solution:

// Inline script in <head>
(function() {
  const theme = localStorage.getItem('theme') || 
    (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
  document.documentElement.setAttribute('data-theme', theme);
})();

Challenge 2: Images in Dark Mode

Problem: Images may look out of place in dark themes Solutions:

  • Use CSS filters: filter: brightness(0.8) contrast(1.2);
  • Provide separate dark/light image variants
  • Use SVGs with currentColor for icons

Challenge 3: Third-party Components

Problem: External components don’t support dark mode Solutions:

  • CSS custom properties inheritance
  • CSS filters for entire component: filter: invert(1) hue-rotate(180deg);
  • Component-specific overrides

Challenge 4: Browser Compatibility

Problem: prefers-color-scheme not supported in older browsers Solution:

const supportsColorScheme = window.matchMedia('(prefers-color-scheme: dark)').media !== 'not all';
if (!supportsColorScheme) {
  // Fallback logic
  setTheme('light');
}

Best Practices & Tips

Performance Optimization

  • Use CSS custom properties for efficient theme switching
  • Minimize DOM manipulation during theme changes
  • Preload critical dark mode assets
  • Use color-scheme meta tag for instant browser UI adaptation

Accessibility Guidelines

  • Maintain contrast ratios: 4.5:1 minimum for normal text
  • Test with screen readers in both modes
  • Provide clear visual indicators for theme state
  • Support high contrast mode preferences

User Experience

  • Remember user preference across sessions
  • Respect system preferences as default
  • Provide smooth transitions between themes
  • Add toggle animation for better feedback

Development Workflow

  • Design dark mode from the start, not as an afterthought
  • Use design tokens/variables for consistent theming
  • Test on multiple devices and lighting conditions
  • Validate color combinations for accessibility

Testing Checklist

  • [ ] Theme persists across browser sessions
  • [ ] No flash of unstyled content (FOUC)
  • [ ] All interactive elements maintain proper contrast
  • [ ] Images and media adapt appropriately
  • [ ] Form inputs remain usable in both themes
  • [ ] Third-party integrations work correctly

Advanced Techniques

System Theme Synchronization

const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');

mediaQuery.addEventListener('change', (e) => {
  if (!localStorage.getItem('theme')) {
    setTheme(e.matches ? 'dark' : 'light');
  }
});

Smooth Theme Transitions

* {
  transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
}

Multiple Theme Support

const themes = {
  light: { bg: '#ffffff', text: '#000000' },
  dark: { bg: '#121212', text: '#ffffff' },
  auto: 'system-preference'
};

Mobile App Considerations

iOS (Swift)

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)
    if traitCollection.userInterfaceStyle != previousTraitCollection?.userInterfaceStyle {
        updateAppearance()
    }
}

Android (Kotlin)

val nightModeFlags = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
when (nightModeFlags) {
    Configuration.UI_MODE_NIGHT_YES -> applyDarkTheme()
    Configuration.UI_MODE_NIGHT_NO -> applyLightTheme()
}

Tools & Resources

Development Tools

  • Chrome DevTools: Test prefers-color-scheme in rendering tab
  • Firefox DevTools: Simulate dark mode in responsive design mode
  • Figma: Design system templates with dark mode variants
  • Storybook: Test components in multiple themes

Color Tools

  • Contrast Checker: WebAIM Contrast Checker for accessibility
  • Coolors.co: Generate dark mode palettes
  • Material Design: Color tool with dark theme support
  • Adobe Color: Extract colors from images for theming

Testing Resources

  • axe-core: Automated accessibility testing
  • Lighthouse: Audit for color contrast issues
  • Color Oracle: Simulate color blindness
  • High Contrast Extensions: Test extreme contrast scenarios

Libraries & Frameworks

  • next-themes: React theme management
  • use-dark-mode: React hook for dark mode
  • vue-dark-mode: Vue.js dark mode plugin
  • styled-theming: Styled-components theming utility

Documentation & Guides

  • MDN Web Docs: CSS prefers-color-scheme
  • Web.dev: Building a color scheme
  • Apple Human Interface Guidelines: Dark Mode
  • Material Design: Dark theme specification

Quick Reference Commands

CSS Variables Setup

:root {
  --color-bg: light;
  --color-text: dark;
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-bg: dark;
    --color-text: light;
  }
}

JavaScript Theme Toggle

const toggle = () => {
  const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
  document.documentElement.setAttribute('data-theme', theme);
  localStorage.setItem('theme', theme);
};

React Hook Pattern

const [theme, setTheme] = useState('light');
useEffect(() => {
  document.documentElement.setAttribute('data-theme', theme);
}, [theme]);

This comprehensive cheatsheet covers all essential aspects of dark mode implementation, from basic concepts to advanced techniques, ensuring you can create professional, accessible dark mode experiences across any platform or framework.

Scroll to Top