Capítulo 82 de 108

Dark Mode (Remix)

Core Idea

Remix implementa dark mode com a lib remix-themes, persistindo o tema em cookie de sessão server-side (via loader/action), o que evita flash de tema mesmo com SSR completo.

Key Concepts

  • remix-themes: lib que integra tema com o loader/action pattern do Remix.
  • createThemeSessionResolver: cria o resolver que lê/grava o tema num createCookieSessionStorage.
  • ThemeProvider (remix-themes): recebe specifiedTheme (do loader) e themeAction (rota de action que grava o cookie).
  • PreventFlashOnWrongTheme: componente que injeta script inline para evitar flash antes da hidratação, recebendo ssrTheme={Boolean(data.theme)}.
  • :root[class~="dark"]: precisa ser adicionado manualmente ao CSS do Tailwind para o seletor dark funcionar corretamente com a abordagem de classe do Remix.

Code Examples

.dark,
:root[class~="dark"] {
  ...;
}
  • O que demonstra: Ajuste necessário no CSS para o dark funcionar via atributo de classe no <html>.
const sessionStorage = createCookieSessionStorage({
  cookie: { name: "theme", path: "/", httpOnly: true, sameSite: "lax", secrets: ["s3cr3t"] },
})
export const themeSessionResolver = createThemeSessionResolver(sessionStorage)
  • O que demonstra: Cookie de sessão dedicado ao tema, separado de outros cookies de auth/sessão.
export async function loader({ request }: LoaderFunctionArgs) {
  const { getTheme } = await themeSessionResolver(request)
  return { theme: getTheme() }
}

export default function AppWithProviders() {
  const data = useLoaderData<typeof loader>()
  return (
    <ThemeProvider specifiedTheme={data.theme} themeAction="/action/set-theme">
      <App />
    </ThemeProvider>
  )
}
  • O que demonstra: Tema resolvido no loader (server) e passado para o provider, garantindo SSR correto sem flash.
import { createThemeAction } from "remix-themes"
export const action = createThemeAction(themeSessionResolver)
  • O que demonstra: Rota de action dedicada que persiste o tema no cookie quando o usuário troca via ModeToggle.

Anti-patterns

  • Não criar a rota /action/set-theme: o ThemeProvider referencia themeAction explicitamente; sem a rota correspondente, o toggle falha ao persistir.

Key Takeaways

  1. Abordagem 100% server-driven (cookie de sessão), diferente de next-themes (client-driven com localStorage).
  2. Requer editar tailwind.css manualmente para adicionar o seletor :root[class~="dark"].
  3. PreventFlashOnWrongTheme é o mecanismo Remix-específico para evitar flash, análogo ao script inline do Astro.
  4. useTheme() de remix-themes retorna tupla [theme, setTheme], e usa enum Theme.LIGHT/Theme.DARK em vez de strings soltas.

Connects To

  • dark-mode: índice geral.