Modo escuro
Alterne entre claro e escuro com a classe .dark, em Next.js, Vite ou Laravel + Inertia.
Todo tema Lens traz valores para claro e escuro. O modo escuro é ativado pela classe .dark: com ela no <html>, os
tokens passam a usar os valores escuros. Sem ela, valem os claros.
<html lang="pt-BR" data-theme="neutro" class="dark">A variante dark: do Tailwind segue a mesma classe. Na maioria dos casos você não precisa dela: bg-background e
text-foreground já trocam de valor sozinhos. Reserve dark: para exceções, como trocar uma imagem.
Next.js
Use o next-themes. Ele aplica a classe antes da página aparecer, então não há flash de tema claro.
Instale
npm install next-themesCrie o provider
"use client"
import { ThemeProvider as NextThemesProvider } from "next-themes"
export function ThemeProvider(props: React.ComponentProps<typeof NextThemesProvider>) {
return <NextThemesProvider {...props} />
}Envolva o layout
attribute="class" faz o next-themes usar .dark. defaultTheme="system" segue a preferência do sistema
operacional até a pessoa escolher.
import { ThemeProvider } from "@/components/theme-provider"
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="pt-BR" data-theme="neutro" suppressHydrationWarning>
<body>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
{children}
</ThemeProvider>
</body>
</html>
)
}O suppressHydrationWarning é necessário: o next-themes altera a classe do <html> antes do React hidratar.
Adicione um botão de alternância
npx shadcn@latest add @lenstech/button"use client"
import { Moon, Sun } from "lucide-react"
import { useTheme } from "next-themes"
import { Button } from "@/components/ui/button"
export function ModeToggle() {
const { resolvedTheme, setTheme } = useTheme()
return (
<Button
variant="ghost"
size="icon"
className="relative"
aria-label="Alternar modo escuro"
onPress={() => setTheme(resolvedTheme === "dark" ? "light" : "dark")}
>
<Sun className="scale-100 rotate-0 transition-transform dark:scale-0 dark:-rotate-90" />
<Moon className="absolute scale-0 rotate-90 transition-transform dark:scale-100 dark:rotate-0" />
</Button>
)
}Os dois ícones ficam no HTML e o CSS escolhe qual mostrar. Assim o botão não depende do tema já ter carregado no cliente.
data-theme não é o tema do next-themes
O next-themes cuida só de claro, escuro e sistema, pela classe. O data-theme escolhe a marca do cliente e fica
fixo no <html>. Não passe attribute="data-theme" para o next-themes: ele sobrescreveria o tema do cliente.
Vite e Laravel + Inertia
Sem next-themes, um script pequeno resolve. Ele precisa rodar no <head>, antes do CSS pintar a página, para evitar
o flash de tema claro.
<head>
<script>
try {
const salvo = localStorage.getItem("modo")
const escuro = salvo ? salvo === "escuro" : matchMedia("(prefers-color-scheme: dark)").matches
document.documentElement.classList.toggle("dark", escuro)
} catch {}
</script>
</head>No Laravel, o mesmo script vai no <head> de resources/views/app.blade.php. O starter kit de React já traz uma
solução equivalente (o hook useAppearance); se o seu projeto usa o starter kit, mantenha a dele.
Para alternar, troque a classe e salve a escolha:
import { Moon, Sun } from "lucide-react"
import { Button } from "@/components/ui/button"
export function ModeToggle() {
function alternar() {
const escuro = document.documentElement.classList.toggle("dark")
try {
localStorage.setItem("modo", escuro ? "escuro" : "claro")
} catch {}
}
return (
<Button variant="ghost" size="icon" className="relative" aria-label="Alternar modo escuro" onPress={alternar}>
<Sun className="scale-100 rotate-0 transition-transform dark:scale-0 dark:-rotate-90" />
<Moon className="absolute scale-0 rotate-90 transition-transform dark:scale-100 dark:rotate-0" />
</Button>
)
}Escuro só em uma seção
.dark funciona em qualquer elemento, não só no <html>. Tudo dentro dele usa os valores escuros. É útil para um
rodapé ou uma faixa de destaque escura numa página clara.
<footer className="dark bg-background text-foreground">
<p className="text-muted-foreground">Atendimento de segunda a sexta, das 8h às 18h.</p>
</footer>Aplique bg-background e text-foreground no próprio elemento. A classe troca os valores das variáveis, mas o fundo
só muda se algo usar o token.
Tema de cliente e modo juntos
data-theme escolhe a marca. .dark escolhe o modo. Os dois são independentes e podem ficar no mesmo elemento ou em
elementos aninhados.
| Marcação | Resultado |
|---|---|
<html data-theme="clinica-sorriso"> | Clínica Sorriso, claro. |
<html data-theme="clinica-sorriso" class="dark"> | Clínica Sorriso, escuro. |
<html class="dark"> + <section data-theme="exemplo-verde"> | Seção com o tema verde, escuro. |
<html data-theme="exemplo-verde"> + <section class="dark"> | Seção com o tema verde, escuro; o resto da página, claro. |
Um projeto de cliente normalmente tem um tema só: o theme-<name> grava as cores em :root e .dark. As combinações
acima importam quando você carrega vários temas de uma vez, como neste site. Use o seletor de tema e o botão de modo
no topo da página para ver os dois funcionando juntos.