Prompt-to-PR: Adicionar Modo Escuro
SOP completa para adicionar modo escuro sem cintilação a um aplicativo Next.js Tailwind usando next-themes — estratégia de classe, preferência do sistema e alternância persistida.
CursorClaude CodeCodexWindsurf Next.jsTypeScriptTailwind
Modo escuro feito de forma errada causa um flash de tema incorreto (FOIT) a cada carregamento de página. Este guia produz uma implementação sem cintilação usando next-themes com a estratégia class do Tailwind.
1. Requisito
Adicione uma alternância persistente de modo escuro/claro/sistema a um site Next.js 15 App Router usando Tailwind. O tema deve ser:
- Sem cintilação no carregamento inicial (sem flash branco no modo escuro).
- Persistido em
localStorage. - Padrão para a preferência do SO (
system). - Alternável através de um botão no cabeçalho do site.
2. Primeiro 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. Alterações Esperadas nos Arquivos
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. Lista de Verificação
tailwind.config.tstemdarkMode: "class"— não"media"(media não permite alternância).ThemeProvideré um Componente Cliente ("use client") envolvendochildrendo layout raiz do Componente Servidor.- O
<html>ou<body>raiz NÃO tem umclass="dark"fixo —next-themesadiciona isso dinamicamente. ThemeTogglerenderizanull(ou um placeholder) atémounted === truepara evitar incompatibilidade de hidratação.- O atributo
suppressHydrationWarningno<html>não está faltando —next-themesexige isso; confirme que está presente na tag<html>. - As variantes
dark:aparecem apenas em contêineres de nível de layout, não em cada elemento individualmente. - A chave
localStorageusada pornext-themesé a padrãotheme— nenhuma chave personalizada que conflite com outro armazenamento.
5. Comandos de Teste
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. Falhas Comuns
- Flash branco no carregamento (FOIT) —
ThemeProvidernão está envolvendo o layout raiz, ousuppressHydrationWarningestá ausente no<html>.next-themesinjeta um script no<head>para definir a classe antes da pintura — isso só funciona quando envolve o elemento mais externo. - Erro de incompatibilidade de hidratação —
ThemeTogglerenderiza conteúdo específico do tema (ícone) antes demounted. Adicioneconst [mounted, setMounted] = useState(false)euseEffect(() => setMounted(true), []). Retorne um placeholder até que mounted seja true. - Classes
dark:não aplicadas —tailwind.config.tsainda temdarkMode: "media"ou a chave está faltando. A estratégia de classe exige o valor"class". - Alternância não visível —
<ThemeToggle>importado em um Componente Servidor sem o limite"use client". A cadeia de importação deve passar por um Componente Cliente.
7. Prompt de Correção
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. Descrição do 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