Modo escuro
Claro, escuro ou o que o sistema escolher, por página ou por ilha. O modo escuro é CSS puro, então a primeira pintura já sai certa.
// você vai aprender
- O que
mode="light" | "dark" | "system"faz, e o que herda esse valor - Por que não há piscada na primeira pintura, e como manter assim com uma escolha salva
- Como colorir a interface do navegador com
theme-color
Experimente
Esta ilha define mode no próprio StyleProvider, independente do que diz a barra "visualizar em". Escolha Sistema e mude a configuração do seu sistema operacional: ela acompanha sem recarregar.
Armazenamento
Como funciona
Todo token de cor é escrito com light-dark(): um valor para cada modo numa única declaração. O navegador escolhe o lado pelo color-scheme, e o data-mode define esse valor:
:root { color-scheme: light dark; }
[data-mode="light"] { color-scheme: light; }
[data-mode="dark"] { color-scheme: dark; }
[data-mode="system"] { color-scheme: light dark; }
:where(:root, [data-style]) {
--cml-surface: light-dark(#ffffff, #09090b);
--cml-fg: light-dark(#09090b, #fafafa);
}color-scheme: light dark quer dizer "siga o prefers-color-scheme". O color-scheme é herdado, então um provider sem mode simplesmente pega o do pai, e a raiz da página segue o sistema sem script nenhum. As partes nativas (barras de rolagem, seletores de data, preenchimento automático) acompanham também.
mode | data-mode | Resultado |
|---|---|---|
| omitido | nenhum | Herda do pai; na raiz, segue o sistema |
"system" | system | Segue o sistema, mesmo dentro de um pai claro ou escuro |
"light" | light | Sempre claro |
"dark" | dark | Sempre escuro |
Diálogos, menus e toasts renderizam num portal. Eles levam o modo do provider pelo contexto do React, então um popup aberto a partir de uma ilha escura também é escuro.
Escolha um padrão
Na maioria dos apps, omita o mode ou use "system" no provider do topo: a página acompanha o sistema operacional desde o primeiro byte. Use um modo fixo em áreas que precisam ficar sempre iguais, como um painel de código escuro ou uma prévia de impressão clara.
<StyleProvider styleName="minimal" mode="system">
<App />
<StyleProvider styleName="minimal" mode="dark">
<CodePanel />
</StyleProvider>
</StyleProvider>
Salve a escolha de quem visita sem piscar
Um botão que guarda a escolha no localStorage e a aplica depois da hidratação pinta o modo errado primeiro. Em vez disso, renderize o modo salvo no servidor: guarde-o num cookie e espalhe no <html> com styleScopeProps, que é seguro de chamar num Server Component.
import { cookies } from "next/headers";
import { styleScopeProps } from "@camaleo/react/style-scope";
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const saved = (await cookies()).get("mode")?.value;
const mode = saved === "light" || saved === "dark" ? saved : "system";
return (
<html lang="pt-BR" {...styleScopeProps({ styleName: "minimal", mode })}>
<body>{children}</body>
</html>
);
}
O botão grava o cookie e o atributo; nada precisa renderizar de novo:
"use client";
export function setMode(mode: "light" | "dark" | "system") {
document.cookie = `mode=${mode}; path=/; max-age=31536000; samesite=lax`;
document.documentElement.setAttribute("data-mode", mode);
}
Não dê mode aos providers internos se a raiz cuida disso: um mode fixo num provider aninhado vence o botão. E ler cookies() faz a rota renderizar a cada requisição.
Num site estático ou numa SPA sem servidor, a única opção sem piscada é um pequeno script inline no <head> que roda antes da primeira pintura:
<script>
try {
document.documentElement.setAttribute("data-mode", localStorage.getItem("mode") || "system");
} catch {}
</script>Pinte a interface do navegador
O theme-color tinge a barra de endereço e a tela de apps recentes nos navegadores de celular. Dê a ele um valor por esquema, usando a cor de superfície do seu estilo. No Next, exporte-o do layout raiz:
import type { Viewport } from "next";
export const viewport: Viewport = {
themeColor: [
{ media: "(prefers-color-scheme: light)", color: "#ffffff" },
{ media: "(prefers-color-scheme: dark)", color: "#09090b" },
],
};
As media queries seguem o sistema. Se quem visita pode forçar um modo, o botão também deve trocar o content das duas tags theme-color para a cor do modo forçado.
Antes de publicar
- O provider do topo usa
"system"ou nenhummode - Uma escolha salva é renderizada pelo servidor, ou aplicada por um script inline antes da pintura
- Só as áreas que precisam ficar sempre iguais têm um
modefixo - O
theme-colorbate com a superfície do estilo nos dois modos