Skip to content

Dark Mode Contrast

DodaTech 4 min read

title: "Dark Mode Contrast" weight: 22 description: "Learn how to ensure sufficient color contrast in dark mode: lighter text on dark backgrounds, avoiding pure white text on pure black, maintaining contrast for interactive states, and using semantic color tokens." date: 2026-06-28 lastmod: 2026-06-28 tags: [accessibility, color-contrast]


Dark mode requires careful contrast management because the same contrast ratios behave differently on dark backgrounds -- pure white text (#fff) on pure black (#000) has a ratio over 21:1, which can cause eye strain, while mid-gray text may become illegible.

## What You'll Learn

You will design accessible dark mode color palettes, test contrast on dark backgrounds, avoid common dark mode pitfalls, and implement dark mode with CSS custom properties.

## Why It Matters

Dark mode is requested by many users, but poorly implemented dark mode can be harder to read than light mode. Text needs sufficient brightness to be legible, and interactive elements need adequate contrast against dark surfaces.

## Real-World Use

A site implements dark mode by inverting colors. White text on dark gray works well, but links change from blue to light blue that has only 2.5:1 contrast against the dark background. Users cannot read the links. The fix uses a lighter blue (#66B3FF) for links in dark mode.

## Dark Mode Contrast Flow

```mermaid
flowchart TD
  A[Dark Mode Design] --> B[Set Background]
  A --> C[Set Text Colors]
  A --> D[Set Interactive Colors]
  B --> E[Dark #1a1a1a to #2d2d2d]
  C --> F[Text: #e0e0e0 minimum]
  D --> G[Links: 3:1 against bg]
  E --> H[Verify text contrast]
  F --> H
  G --> H

Designing Dark Mode Colors

Use semantic color tokens that adapt between modes rather than hard-coding dark colors.

<!-- Dark mode toggle -->
<button id="theme-toggle" aria-label="Toggle dark mode">
  <span class="icon-light">Light</span>
  <span class="icon-dark">Dark</span>
</button>
/* Semantic color system with dark mode */
:root {
  --bg-primary: #ffffff;
  --bg-secondary: #f5f5f5;
  --text-primary: #1a1a1a;
  --text-secondary: #555555;
  --link-color: #0056b3;
  --link-hover: #003d80;
  --border-color: #cccccc;
}

[data-theme="dark"] {
  --bg-primary: #1a1a1a;
  --bg-secondary: #2d2d2d;
  --text-primary: #e0e0e0;
  --text-secondary: #a0a0a0;
  --link-color: #66b3ff;
  --link-hover: #99ccff;
  --border-color: #444444;
}

/* Verify dark mode contrast */
/* --text-primary #e0e0e0 on --bg-primary #1a1a1a: ~12:1 -- passes */
/* --text-secondary #a0a0a0 on --bg-primary #1a1a1a: ~5.5:1 -- passes AA */
/* --link-color #66b3ff on --bg-primary #1a1a1a: ~4.8:1 -- passes AA */
// Dark mode contrast validator
function validateDarkModeColors() {
  const darkBg = getComputedStyle(document.documentElement)
    .getPropertyValue('--bg-primary').trim();

  const pairs = [
    { name: 'Primary text', fg: '--text-primary' },
    { name: 'Secondary text', fg: '--text-secondary' },
    { name: 'Link text', fg: '--link-color' },
    { name: 'Border', fg: '--border-color' }
  ];

  pairs.forEach(pair => {
    const fg = getComputedStyle(document.documentElement)
      .getPropertyValue(pair.fg).trim();
    const ratio = getContrastRatio(fg, darkBg);
    const passes = ratio >= 4.5;

    console.log(`${pair.name}: ${ratio.toFixed(1)}:1 - ${passes ? 'PASS' : 'FAIL'}`);
  });
}

Common Mistakes

  • Using pure black (#000) as dark mode background
  • Using pure white (#fff) for text on dark backgrounds
  • Simply inverting light mode colors
  • Not testing interactive states (hover, focus) in dark mode
  • Using the same accent colors that work in light mode
  • Not adjusting image and icon brightness for dark mode
  • Forgetting about shadows and elevation in dark mode

Practice and Challenge

Practice 1: Check contrast of all text colors in dark mode on your site. Practice 2: Verify link color in dark mode passes AA. Practice 3: Test focus indicator visibility in dark mode. Practice 4: Check button contrast in both default and hover states. Practice 5: Verify border and divider contrast in dark mode.

Challenge: Create a complete dark mode color system that passes WCAG AA for all text and UI components. Include at least 15 semantic tokens: text (3 levels), background (3 levels), interactive (primary, secondary, danger), link, border, focus, and surface (3 levels). Document the contrast ratio for each foreground-background pair in dark mode.

FAQ

What is the best background color for dark mode?

Dark gray (#1a1a1a to #2d2d2d) is better than pure black. It reduces eye strain while maintaining contrast.

Should I use pure white text in dark mode?

Use off-white (#e0e0e0) instead of pure white to reduce eye strain while maintaining contrast.

How do I handle images in dark mode?

Use CSS filters to reduce image brightness. Add a semi-transparent overlay for images with text.

Do I need separate focus indicators for dark mode?

Yes. Focus indicators that work in light mode may not have sufficient contrast in dark mode.

What about dark mode for forms?

Input backgrounds, borders, and labels all need separate dark mode values. Test each for contrast.

Can I use a dark mode automatic?

Use the prefers-color-scheme media query to detect system preference and apply dark mode automatically.

Mini Project

Create a dark mode contrast testing tool. Load a page in dark mode, extract all CSS custom properties, and validate contrast for every semantic color pair. Generate a report showing which pairs pass and fail, with suggested alternative colors for failures.

What's Next

Gradient and Background Contrast covers contrast testing for gradients and complex backgrounds.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro