Capítulo 80 de 108

Dark Mode (Vite)

Core Idea

Sem SSR, um app Vite implementa dark mode com um ThemeProvider customizado próprio (Context API) que lê/grava localStorage e aplica a classe dark/light diretamente no <html>.

Key Concepts

  • ThemeProviderContext: React.createContext customizado, sem dependência externa.
  • storageKey: chave configurável de localStorage (default "vite-ui-theme"), permite múltiplos apps na mesma origem sem conflito.
  • theme === "system": resolvido via window.matchMedia("(prefers-color-scheme: dark)") em tempo de efeito, não reativo a mudanças do SO após o mount.
  • useTheme(): hook customizado que lança erro se usado fora do provider.

Code Examples

const [theme, setTheme] = useState<Theme>(
  () => (localStorage.getItem(storageKey) as Theme) || defaultTheme
)

useEffect(() => {
  const root = window.document.documentElement
  root.classList.remove("light", "dark")
  if (theme === "system") {
    const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"
    root.classList.add(systemTheme)
    return
  }
  root.classList.add(theme)
}, [theme])
  • O que demonstra: Aplicação manual da classe de tema no documentElement, sem lib externa.
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
  {children}
</ThemeProvider>
  • O que demonstra: Uso direto do provider próprio no root do app Vite.

Anti-patterns

  • Esperar reatividade a mudança de tema do SO em tempo real: essa implementação só resolve system no mount/mudança de estado, não escuta o evento change do matchMedia.

Key Takeaways

  1. Vite não tem SSR, então não há necessidade de suppressHydrationWarning nem de lib como next-themes.
  2. O provider é ~50 linhas copiadas para o projeto (não é dependência npm), dando controle total.
  3. setTheme sempre persiste em localStorage antes de atualizar o state.

Connects To

  • dark-mode: índice geral.
  • dark-mode/next: contraste, usa lib e cuida de SSR.