Back to Blog
tutorialsDesignUI/UXDark ModeAccessibilityCSS

Designing for Dark Mode: A Complete Guide

Everything you need to know about implementing dark mode that users actually love, from color theory to accessibility.

December 18, 2024
5 min read
Pulore Team
Designing for Dark Mode: A Complete Guide

Designing for Dark Mode: A Complete Guide

Dark mode has evolved from a nice-to-have feature to an expected standard. But doing it well requires more than inverting colors. Here's everything we've learned about creating dark interfaces that are beautiful, accessible, and functional.

Why dark mode matters

The practical benefits

  1. Reduced eye strain in low-light environments
  2. Battery savings on OLED screens (up to 60% for pure black)
  3. Accessibility for users with light sensitivity
  4. User preference — many simply prefer it

The business case

Our analytics consistently show:

  • 65-80% of users enable dark mode when available
  • Longer session times in low-light hours
  • Higher engagement scores for apps with thoughtful dark mode

The common mistakes

Before we dive into best practices, let's address what not to do:

❌ Mistake 1: Just inverting colors

/* Don't do this */
filter: invert(1);

This creates harsh contrasts and inverts images, creating a jarring experience.

❌ Mistake 2: Pure black backgrounds

/* Avoid this */
background: #000000;

Pure black (#000000) creates too much contrast with text and can cause "halation" — a visual effect where white text appears to bleed into black backgrounds.

❌ Mistake 3: Ignoring color saturation

Colors that work in light mode become overwhelming in dark mode. You need to reduce saturation for dark themes.

Building a dark mode color system

Background layers

Instead of pure black, use graduated dark grays:

:root {
  /* Dark mode backgrounds */
  --bg-void: #050508; /* Deepest layer */
  --bg-deep: #0a0a0f; /* Primary background */
  --bg-surface: #12121a; /* Elevated surfaces */
  --bg-elevated: #1a1a24; /* Cards, modals */
  --bg-hover: #242430; /* Hover states */
}

This creates depth through subtle gradation rather than harsh lines.

Text colors

Avoid pure white text on dark backgrounds:

:root {
  --text-primary: #f4f4f6; /* 96% white */
  --text-secondary: #a1a1aa; /* 60% white */
  --text-tertiary: #71717a; /* 40% white */
}

This slight reduction from pure white significantly improves readability.

Accent colors

Reduce saturation for dark mode accents:

/* Light mode */
--accent-blue: hsl(210, 100%, 50%);
 
/* Dark mode — reduce saturation */
--accent-blue-dark: hsl(210, 80%, 60%);

Implementation strategies

Strategy 1: CSS custom properties

The most flexible approach:

:root {
  --bg-primary: #ffffff;
  --text-primary: #1a1a1a;
}
 
[data-theme="dark"] {
  --bg-primary: #0a0a0f;
  --text-primary: #f4f4f6;
}
 
body {
  background: var(--bg-primary);
  color: var(--text-primary);
}

Strategy 2: Tailwind CSS

If you're using Tailwind:

// tailwind.config.js
module.exports = {
  darkMode: "class", // or 'media'
  // ...
};
<div class="bg-white dark:bg-gray-900">
  <p class="text-gray-900 dark:text-gray-100">Content</p>
</div>

Strategy 3: Respect system preferences

// Check system preference
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
 
// Listen for changes
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", e => {
  setTheme(e.matches ? "dark" : "light");
});

Handling images and media

Images

Options for handling images in dark mode:

  1. Reduce brightness/contrast
[data-theme="dark"] img {
  filter: brightness(0.9) contrast(1.1);
}
  1. Provide alternate versions
<picture>
  <source srcset="hero-dark.jpg" media="(prefers-color-scheme: dark)" />
  <img src="hero-light.jpg" alt="Hero image" />
</picture>
  1. Use transparent backgrounds for illustrations and logos

Shadows

Shadows don't work the same in dark mode:

/* Light mode */
.card {
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
 
/* Dark mode — use glow effects instead */
[data-theme="dark"] .card {
  box-shadow: 0 0 30px rgba(0, 128, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.05);
}

Accessibility considerations

Contrast ratios

WCAG 2.1 requirements still apply:

ElementMinimum ratio
Normal text4.5:1
Large text (18px+)3:1
UI components3:1

Use tools like WebAIM Contrast Checker to verify.

Don't rely on color alone

Information conveyed by color should also be conveyed by:

  • Icons or symbols
  • Text labels
  • Patterns or textures

Motion and animations

Some users are more sensitive to motion in dark mode. Respect preferences:

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

Testing your dark mode

Manual testing checklist

  • All text is readable (check contrast ratios)
  • Images don't look washed out or too dark
  • Borders and dividers are visible
  • Focus states are clear
  • Hover states are noticeable
  • Error/success states are distinguishable
  • No elements "disappear" in dark mode

Tools we use

  • Chrome DevTools — Rendering tab for color scheme emulation
  • Polypane — Side-by-side light/dark comparison
  • aXe — Accessibility auditing
  • Stark — Figma plugin for contrast checking

Key takeaways

  1. Never just invert — build a proper dark color system
  2. Avoid pure black — use dark grays for depth
  3. Reduce saturation — colors need adjustment for dark mode
  4. Test thoroughly — accessibility requirements still apply
  5. Respect preferences — support system-level settings

Building a product that needs dark mode done right? We can help design and implement a color system that works beautifully in any lighting.

Pulore Team
Design
Share:

Want to discuss this topic?

We love talking about software architecture, development best practices, and technical strategy.