usability.cat
Issue Wiki

Dark Mode Issues

Dark themes look cool until your visitors can't read the text. Here's how to build a dark mode that's stylish and actually usable.

What is this?

Dark mode is like wearing sunglasses indoors. Stylish? Sure. But if you can't see the menu, read the fine print, or find the checkout button, the style isn't doing its job. Dark themes introduce a whole category of readability problems that don't exist in light mode: gray text on dark gray backgrounds, images with dark edges disappearing into the void, pure white text that sears your retinas at 2 AM. The cat lives in the dark and still demands legibility.

Why it matters

  • For your visitors: Many people prefer dark mode — it's easier on the eyes in low light and uses less battery on OLED screens. But a badly implemented dark theme is worse than no dark theme at all. If text contrast is too low, your visitors are squinting. If it's too high (pure white on pure black), their eyes fatigue quickly. Either way, they leave.

  • For your business: Dark mode is increasingly expected. Over 80% of users report using dark mode at least sometimes. But shipping a broken dark theme tells visitors "we don't test our product." That's a trust hit you can't afford, especially on a landing page or pricing page where conversions happen.

  • The standard: WCAG requires a minimum contrast ratio of 4.5:1 for body text and 3:1 for large text. In dark mode, this means your background shouldn't be pure black (#000) and your text shouldn't be pure white (#fff). Use off-black backgrounds (oklch ~0.07-0.12) and slightly muted text (oklch ~0.85-0.93) for comfortable reading.

Comfortable dark theme
:root {
  /* Soft dark — not pure black */
  --bg: oklch(0.1 0.005 75);
  --bg-raised: oklch(0.14 0.005 75);
  /* Muted white — not eye-searing */
  --text: oklch(0.9 0.005 75);
  --text-muted: oklch(0.6 0.005 75);
  /* Borders visible but subtle */
  --border: oklch(0.25 0.005 75);
}
Eye strain dark mode
:root {
  /* Pure black — harsh */
  --bg: #000000;
  /* Pure white — blinding */
  --text: #ffffff;
  /* Invisible borders */
  --border: #111111;
  /* Unreadable muted text */
  --text-muted: #333333;
}

How to fix it

React / Next.js

Set up proper dark mode colors with comfortable contrast in your Tailwind theme:

// A well-designed dark card component
export function DarkCard({ title, description }: { title: string; description: string }) {
  return (
    <div className="border border-neutral-800 bg-neutral-900 p-6">
      {/* High contrast for headings — but not pure white */}
      <h3 className="text-lg font-semibold text-neutral-100">{title}</h3>
      {/* Lower contrast for secondary text — but still readable */}
      <p className="mt-2 text-sm text-neutral-400">{description}</p>
      {/* Primary action is the brightest element */}
      <button className="mt-4 bg-amber-500 px-4 py-2 text-sm font-semibold text-neutral-950">
        Learn more
      </button>
    </div>
  );
}

Key rules: use text-neutral-100 (not text-white) for headings, text-neutral-400 (not text-neutral-600) for secondary text, and bg-neutral-900 (not bg-black) for surfaces.

Plain HTML / CSS

/* Dark mode color system with proper contrast */
:root {
  --bg-base: oklch(0.1 0.005 75); /* near-black, not pure black */
  --bg-surface: oklch(0.14 0.005 75); /* raised surfaces */
  --bg-hover: oklch(0.18 0.005 75); /* hover states */
  --text-high: oklch(0.9 0.005 75); /* headings — ~13:1 contrast */
  --text-mid: oklch(0.65 0.005 75); /* body text — ~6:1 contrast */
  --text-low: oklch(0.5 0.005 75); /* captions — ~4.5:1 contrast */
  --border: oklch(0.25 0.01 75); /* visible but subtle */
}

/* Test: can you read this on the background? */
body {
  background: var(--bg-base);
  color: var(--text-mid);
}
h1,
h2,
h3 {
  color: var(--text-high);
}
.caption,
.label {
  color: var(--text-low);
} /* still 4.5:1 minimum */

/* Images: add subtle borders so dark images don't vanish */
img {
  border: 1px solid var(--border);
  border-radius: 0.5rem;
}
High impactvisualDesign~2 paws

Dark mode contrast issues are among the most common problems the cat finds. Low-contrast text in dark themes is practically invisible on some monitors. This one matters.

How the cat scores this

The scanner renders your page and measures the actual computed contrast ratio between text and its background. It specifically checks for: text below 4.5:1 contrast ratio, pure black backgrounds paired with pure white text (the "harsh dark mode" pattern), borders and dividers that disappear against the background, and images without visible boundaries. Each failing element lowers the score.

Further reading

On this page