---
name: add-dark-mode-toggle
description: Adds a light/dark/system theme toggle that persists the choice in localStorage and avoids the white flash on page load, using CSS variables or Tailwind's dark class. Use when the user asks for dark mode, a night theme, a theme switcher, or follow-the-system colour scheme.
license: CC-BY-4.0
metadata:
  category: frontend
  tags: "react,tailwind,css,theming"
  author: johnny-bravo
  version: "1.0"
  updated: "2026-10-30"
  status: published
---

# Add a dark mode toggle

## When to use
- The app has one colour scheme and the user wants dark mode or a theme switcher.

## Steps
1. Decide the mechanism: Tailwind v4 `@custom-variant dark (&:where(.dark, .dark *));` (v3: `darkMode: "class"`), or plain CSS variables under `:root` and `.dark`.
2. Add a tiny blocking script in `<head>` **before** any CSS paints, so there is no flash:
   ```html
   <script>
     const t = localStorage.theme ?? "system";
     const dark = t === "dark" || (t === "system" && matchMedia("(prefers-color-scheme: dark)").matches);
     document.documentElement.classList.toggle("dark", dark);
   </script>
   ```
3. Build a toggle with three states (light, dark, system) that writes `localStorage.theme` and toggles the `dark` class.
4. When on "system", listen to `matchMedia(...).addEventListener("change", ...)` and update live.
5. Add `color-scheme: light dark` so native inputs and scrollbars match.
6. Check contrast of text, borders and focus rings in both themes.

## Gotchas
- In Next.js, add `suppressHydrationWarning` on `<html>` because the class is set before React hydrates (or use `next-themes`).
- Images and shadows that look fine on white may vanish on dark; review them.

## Example prompt
"Add a dark mode switch to the navbar that remembers my choice."
