Building Accessible Color Palettes for Dark Mode

Dark mode has evolved from a novelty to an expectation. But implementing it accessibly requires more than inverting your colors. WCAG compliance in dark mode presents unique challenges that many designers overlook.

Dark Mode Challenges

Dark mode isn't simply light mode with inverted colors. It introduces several accessibility considerations that require intentional design decisions.

The Pure Black Problem

Many designers reach for #000000 when implementing dark mode. This is almost always a mistake. Pure black backgrounds create excessive contrast with white text, causing eye strain and making text harder to read for extended periods.

Poor: Pure Black

This text on pure black creates harsh contrast. While it technically passes WCAG contrast ratios, it causes eye strain during extended reading sessions.

Good: Elevated Black

This elevated black (#0F0F0F) with slightly dimmed white (#E5E5E5) is easier on the eyes while maintaining excellent readability and WCAG AA compliance.

Halation Effect

White or bright text on dark backgrounds can create a "halation" or "glow" effect, especially for users with astigmatism. This makes text appear fuzzy or blurry. The solution: slightly reduce text brightness.

Pro Tip: Use #E5E5E5 or #DEDEDE for body text on dark backgrounds instead of pure white. This maintains contrast while reducing halation.

Contrast in Dark Mode

WCAG AA requires a 4.5:1 contrast ratio for normal text and 3:1 for large text. This doesn't change in dark mode—but achieving it does.

The Math Still Matters

Just because your light mode palette passes WCAG doesn't mean the inverted version will. You need to test every color combination independently.

Dark Mode Contrast Examples

#E5E5E5 on #0F0F0F 13.2:1 ✓ AAA
#B8B8B8 on #0F0F0F 5.2:1 ✓ AA
#888888 on #0F0F0F 2.9:1 ✗ Fails

Secondary Text Colors

In light mode, you might use #6B6B6B for secondary text. In dark mode, you need a different shade:

/* Light mode */ :root { --text-primary: #1A1A1A; /* 13.1:1 on white */ --text-secondary: #6B6B6B; /* 4.6:1 on white - AA ✓ */ } /* Dark mode */ :root { --text-primary: #E5E5E5; /* 13.2:1 on #0F0F0F */ --text-secondary: #A8A8A8; /* 4.5:1 on #0F0F0F - AA ✓ */ }

Color Shifts and Perception

Colors appear differently on dark backgrounds. A vibrant blue that looks perfect in light mode might be overwhelming in dark mode.

Desaturate Bright Colors

Highly saturated colors on dark backgrounds can cause visual vibration and eye strain. The solution: reduce saturation by 15-25% for dark mode.

/* Light mode accent */ --accent-light: hsl(220, 90%, 55%); /* Vibrant blue */ /* Dark mode accent - reduced saturation */ --accent-dark: hsl(220, 75%, 60%); /* Softer, easier on eyes */

Increase Lightness

Dark colors that work as accents in light mode become invisible in dark mode. Increase their lightness value:

Light Mode
Primary Button
Dark Mode
Primary Button

Borders and Dividers

Subtle borders in light mode need to be brighter in dark mode to remain visible:

/* Light mode */ --border: #E5E5E5; /* Light gray */ /* Dark mode */ --border: #2A2A2A; /* Must be brighter than bg */

Testing Methods

1. Use Browser DevTools

Chrome and Firefox have built-in emulation for prefers-color-scheme:

2. Contrast Checkers

Test every text/background combination:

3. Real Device Testing

OLED screens render pure black differently than LCD. Test on actual devices:

4. Accessibility Extensions

Implementation Guide

CSS Custom Properties Approach

The cleanest way to implement dark mode is with CSS variables and prefers-color-scheme:

/* Define all colors in :root */ :root { /* Light mode (default) */ --bg-primary: #FAFAFA; --bg-secondary: #FFFFFF; --text-primary: #1A1A1A; --text-secondary: #6B6B6B; --border: #E5E5E5; --accent: #2D5BE3; } /* Dark mode overrides */ @media (prefers-color-scheme: dark) { :root { --bg-primary: #0F0F0F; --bg-secondary: #1A1A1A; --text-primary: #E5E5E5; --text-secondary: #A8A8A8; --border: #2A2A2A; --accent: #5B8EF4; /* Lighter for dark bg */ } } /* Use variables everywhere */ body { background: var(--bg-primary); color: var(--text-primary); transition: background 0.3s, color 0.3s; }

Manual Toggle (Optional)

Some users prefer to control dark mode independent of system settings:

/* Add data attribute to html */ html[data-theme="dark"] { --bg-primary: #0F0F0F; --text-primary: #E5E5E5; /* ... rest of dark colors */ } /* JavaScript toggle */ const toggle = document.querySelector('.theme-toggle'); toggle.addEventListener('click', () => { const html = document.documentElement; const current = html.dataset.theme; html.dataset.theme = current === 'dark' ? 'light' : 'dark'; localStorage.setItem('theme', html.dataset.theme); });

Complete Accessible Dark Mode Palette

:root { /* Backgrounds */ --bg-primary: #0F0F0F; /* Main background */ --bg-secondary: #1A1A1A; /* Cards, elevated surfaces */ --bg-tertiary: #242424; /* Hover states */ /* Text */ --text-primary: #E5E5E5; /* 13.2:1 - Body text */ --text-secondary: #A8A8A8; /* 4.5:1 - Secondary text */ --text-tertiary: #808080; /* 3.2:1 - Disabled text (large only) */ /* Borders */ --border: #2A2A2A; /* Subtle dividers */ --border-light: #1F1F1F; /* Extra subtle */ /* Accents */ --accent-blue: #5B8EF4; /* Primary actions */ --accent-green: #4ADE80; /* Success states */ --accent-red: #F87171; /* Errors/warnings */ --accent-yellow: #FCD34D; /* Highlights */ }

Image Handling

Images with white backgrounds look jarring in dark mode. Consider these approaches:

/* Reduce brightness of images in dark mode */ @media (prefers-color-scheme: dark) { img { opacity: 0.85; } /* Or add subtle border */ img { border: 1px solid var(--border); border-radius: 8px; } }

Testing Checklist

  1. Verify all text meets WCAG AA contrast (4.5:1 minimum)
  2. Check large text meets WCAG AA contrast (3:1 minimum)
  3. Test on OLED screen for halation effects
  4. Validate focus indicators are visible
  5. Ensure images have adequate treatment
  6. Test with actual users who prefer dark mode
  7. Run automated accessibility audits (axe, WAVE)

Conclusion

Accessible dark mode requires deliberate color choices that go beyond simple inversion. By using elevated blacks, maintaining proper contrast ratios, adjusting color saturation, and thoroughly testing your palette, you can create dark mode experiences that are both beautiful and accessible to all users.

Remember: dark mode is about reducing eye strain and improving readability in low-light conditions. If your dark mode causes more strain than light mode, you've missed the point.