Prompt-to-PR : Ajouter le mode sombre
Procédure opérationnelle standard de bout en bout pour ajouter un mode sombre sans scintillement à une application Next.js Tailwind utilisant next-themes — stratégie de classe, préférence système et bascule persistante.
CursorClaude CodeCodexWindsurf Next.jsTypeScriptTailwind
Un mode sombre mal implémenté provoque un flash de thème incorrect (FOIT) à chaque chargement de page. Ce playbook produit une implémentation sans scintillement utilisant next-themes avec la stratégie class de Tailwind.
1. Exigence
Ajouter une bascule persistante mode sombre/clair/système à un site Next.js 15 App Router utilisant Tailwind. Le thème doit être :
- Sans scintillement au chargement initial (pas de flash blanc en mode sombre).
- Persisté dans
localStorage. - Par défaut, la préférence du système (
system). - Bascule via un bouton dans l’en-tête du site.
2. Premier prompt
Add flicker-free dark mode to this Next.js 15 App Router project with Tailwind.
Requirements:1. Install `next-themes`.2. In `tailwind.config.ts`, set `darkMode: "class"`.3. Create `src/components/ThemeProvider.tsx` — a thin Client Component wrapper around `next-themes` ThemeProvider: <ThemeProvider attribute="class" defaultTheme="system" enableSystem> {children} </ThemeProvider> Mark it "use client".4. Wrap the root layout's `<body>` with ThemeProvider in `src/app/layout.tsx`. Keep the body tag; just wrap children.5. Create `src/components/ThemeToggle.tsx` — a Client Component with a button that cycles: light -> dark -> system. Use `useTheme()` from next-themes. Show the current theme as an icon or label. Handle the mounted check to avoid hydration mismatch (render null or a skeleton until mounted).6. Add `<ThemeToggle />` to the existing site header/nav component.7. Do not change any color values or CSS — only add `dark:` Tailwind variants where the existing design clearly has background and text colors that need inversion. Update: bg-white -> bg-white dark:bg-gray-950, text-gray-900 -> text-gray-900 dark:text-gray-100. Do not invert every element — only top-level layout containers.3. Modifications de fichiers attendues
package.json (next-themes)tailwind.config.ts (darkMode: "class")src/app/layout.tsx (ThemeProvider wrapper)src/components/ThemeProvider.tsx (new — "use client" wrapper)src/components/ThemeToggle.tsx (new — theme cycling button)src/components/Header.tsx (add ThemeToggle — or whichever nav file)4. Liste de vérification
tailwind.config.tsadarkMode: "class"— pas"media"(media ne permet pas de bascule).ThemeProviderest un composant client ("use client") enveloppantchildrendu layout racine du composant serveur.- La racine
<html>ou<body>n’a PAS declass="dark"codée en dur —next-themesajoute cela dynamiquement. ThemeTogglerendnull(ou un espace réservé) jusqu’à ce quemounted === truepour éviter une incohérence d’hydratation.- Aucun
suppressHydrationWarningsur<html>manquant —next-themesle nécessite ; confirmez qu’il est présent sur la balise<html>. - Les variantes
dark:n’apparaissent que sur les conteneurs de niveau layout, pas sur chaque élément. - La clé
localStorageutilisée parnext-themesest la valeur par défauttheme— pas de clé personnalisée qui entre en conflit avec d’autres stockages.
5. Commandes de test
bun dev
# In Chrome DevTools: set prefers-color-scheme to dark# Reload — confirm no white flash before dark theme applies
# Toggle the button — confirm light/dark/system cycle# Reload after each — confirm preference persists
bun tsc --noEmitbun run build# Confirm no hydration warnings in the browser console after bun run start6. Échecs courants
- Flash blanc au chargement (FOIT) —
ThemeProvidern’enveloppe pas le layout racine, ousuppressHydrationWarningest manquant sur<html>.next-themesinjecte un script dans<head>pour définir la classe avant le rendu — cela ne fonctionne que s’il enveloppe l’élément le plus externe. - Erreur d’incohérence d’hydratation —
ThemeTogglerend un contenu spécifique au thème (icône) avantmounted. Ajoutezconst [mounted, setMounted] = useState(false)etuseEffect(() => setMounted(true), []). Renvoyez un espace réservé jusqu’à ce que mounted soit true. - Les classes
dark:ne sont pas appliquées —tailwind.config.tsa toujoursdarkMode: "media"ou la clé est manquante. La stratégie de classe nécessite la valeur"class". - Bascule non visible —
<ThemeToggle>importé dans un composant serveur sans limite"use client". La chaîne d’import doit passer par un composant client.
7. Prompt de correction
There is a white flash before dark mode applies on page load.
Confirm:1. In src/app/layout.tsx, the <html> tag has suppressHydrationWarning.2. ThemeProvider wraps {children} directly inside <body>.3. ThemeProvider uses attribute="class" (not attribute="data-theme").
next-themes injects an inline script into <head> that reads localStorage andsets the class on <html> synchronously before paint. This only works if theThemeProvider is rendered above the content it styles.8. Description de la PR
## Feature: Flicker-free dark mode via next-themes
- `tailwind.config.ts`: `darkMode: "class"`- `ThemeProvider` wraps root layout — `next-themes` sets `<html class="dark">` synchronously before first paint (no flash)- `ThemeToggle` cycles light / dark / system; persists to `localStorage`- Mounted guard in ThemeToggle prevents hydration mismatch- `dark:` variants added to top-level layout containers only