Pular para o conteúdo
A
Personalização

Modo escuro sem flash

Modo escuro que segue o sistema, lembra a escolha da pessoa e não pisca branco ao carregar. Receitas para Next.js (App Router) e Vite com Tailwind CSS v4.

Como o modo escuro funciona

O modo escuro é a classe .dark no <html>. Os tokens --aura-* trocam de valor com ela, então todos os componentes mudam juntos, sem re-render. O tema de cores fica em data-theme:

tsx
<html data-theme="aura" class="dark">
  • O ThemeProvider guarda a escolha no localStorage (chaves aura-theme e aura-color-scheme) e, em system, segue o claro/escuro do sistema operacional.
  • Com o preset aura-ui-tailwind, a variante dark: do Tailwind segue a classe .dark e os 8 temas escuros (midnight, cyber, synthwave, matrix, volcano, grape, obsidian, terminal), não só a preferência do sistema.

Por que a tela pisca

O ThemeProvider roda no navegador depois que o HTML já apareceu. Sem ajuda, a primeira pintura sai com o tema padrão (claro) e só depois troca para o escuro: é o "flash" branco. A correção é um script pequeno no <head> que lê a escolha salva e ajusta o <html> antes de qualquer pintura. Este site faz exatamente isso.

Use as mesmas chaves e padrões
O script abaixo usa as chaves e os padrões do ThemeProvider (system e o tema aura). Se você mudar storageKey, colorSchemeKey ou defaultColorScheme no provider, mude também no script.

Next.js (App Router)

Crie o script num arquivo próprio. A lista de temas vem do pacote, então ele continua certo quando surgirem temas novos:

app/theme-script.ts
import { AURA_DARK_THEMES, AURA_THEMES } from "aura-ui-core";

/** Roda no <head> antes da primeira pintura e aplica o tema salvo pelo ThemeProvider. */
export const themeScript = `(function () {
  try {
    var themes = ${JSON.stringify(AURA_THEMES)};
    var darkThemes = ${JSON.stringify(AURA_DARK_THEMES)};
    var mode = localStorage.getItem("aura-theme") || "system";
    var scheme = localStorage.getItem("aura-color-scheme");
    if (themes.indexOf(scheme) < 0) scheme = "aura";
    var named = themes.indexOf(mode) >= 0 ? mode : null;
    var dark = mode === "system"
      ? matchMedia("(prefers-color-scheme: dark)").matches
      : mode === "dark" || darkThemes.indexOf(named) >= 0;
    var html = document.documentElement;
    html.dataset.theme = named || scheme;
    html.classList.toggle("dark", dark);
  } catch (e) {}
})();`;

Coloque-o no <head> do layout raiz. O Providers é o mesmo da instalação (o ThemeProvider num componente "use client"):

app/layout.tsx
import { Providers } from "./providers";
import { themeScript } from "./theme-script";
import "./globals.css";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    // O script muda o <html> antes do React: suppressHydrationWarning evita o aviso.
    <html lang="pt-BR" suppressHydrationWarning>
      <head>
        <script dangerouslySetInnerHTML={{ __html: themeScript }} />
      </head>
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}

Vite (ou React sem SSR)

Sem servidor, o lugar é o index.html: o script roda antes do bundle do React. O ThemeProvider continua no main.tsx, como na instalação.

index.html
<!doctype html>
<html lang="pt-BR">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script>
      (function () {
        try {
          var themes = ["aura","midnight","ocean","forest","sunset","cyber","rose","nord","mocha","synthwave","amber","emerald","cherry","lavender","matrix","arctic","desert","volcano","grape","mint","steel","peach","indigo","lime","sakura","obsidian","solar","aqua","candy","mono","terminal","bubblegum","caramel"];
          var darkThemes = ["midnight","cyber","synthwave","matrix","volcano","grape","obsidian","terminal"];
          var mode = localStorage.getItem("aura-theme") || "system";
          var scheme = localStorage.getItem("aura-color-scheme");
          if (themes.indexOf(scheme) < 0) scheme = "aura";
          var named = themes.indexOf(mode) >= 0 ? mode : null;
          var dark = mode === "system"
            ? matchMedia("(prefers-color-scheme: dark)").matches
            : mode === "dark" || darkThemes.indexOf(named) >= 0;
          var html = document.documentElement;
          html.dataset.theme = named || scheme;
          html.classList.toggle("dark", dark);
        } catch (e) {}
      })();
    </script>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

Aqui a lista de temas fica escrita no script. Ao atualizar o aura-ui-core, confira se surgiram temas novos (AURA_THEMES e AURA_DARK_THEMES).

Botão de claro/escuro

O hook useTheme devolve o modo efetivo (resolvedTheme, já considerando o sistema) e toggleMode:

mode-toggle.tsx
"use client";
import { Moon, Sun } from "lucide-react";
import { Button } from "aura-ui-components/button";
import { useTheme } from "aura-ui-core/client";

export function ModeToggle() {
  const { resolvedTheme, toggleMode } = useTheme();
  const dark = resolvedTheme === "dark";
  return (
    <Button
      variant="ghost"
      size="icon"
      onClick={toggleMode}
      aria-label={dark ? "Usar tema claro" : "Usar tema escuro"}
    >
      {dark ? <Sun /> : <Moon />}
    </Button>
  );
}

Para oferecer as três opções (claro, escuro e sistema), use setTheme:

tsx
const { setTheme } = useTheme();

setTheme("dark");   // sempre escuro
setTheme("light");  // sempre claro
setTheme("system"); // volta a seguir o sistema operacional

Quer um seletor pronto com os 33 temas e o modo? Use o ThemeSwitcher.

Cores no modo escuro

Use os tokens (bg-aura-surface, text-aura-muted-foreground, border-aura-border…) em vez de cores fixas: eles já têm o valor do escuro. Deixe dark: para exceções:

tsx
{/* Tokens já trocam sozinhos no escuro: prefira-os */}
<div className="bg-aura-surface text-aura-surface-foreground border-aura-border" />

{/* dark: para exceções pontuais */}
<img className="dark:brightness-90" src="/foto.jpg" alt="" />

Para ajustar a cor da sua marca só no escuro, sobrescreva os tokens em .dark[data-theme="minha-marca"] (veja Temas) ou monte o tema no gerador de temas, que confere o contraste nos dois modos.

Barras de rolagem e campos nativos

Barras de rolagem, o calendário do <input type="date"> e o fundo do autofill são do navegador. A propriedade color-scheme faz eles seguirem o modo:

globals.css
/* globals.css — barras de rolagem, inputs de data e o autofill seguem o modo */
:root {
  color-scheme: light;
}
:root.dark {
  color-scheme: dark;
}