Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIntermediáriocódigo testado

Dark mode no Tailwind: a variante dark e o botão de tema

Como dark: funciona, como trocar do prefers-color-scheme para uma classe no html e como guardar a escolha do usuário sem o flash de tela branca.

Rodolfo Mori4 min de leitura

dark: aplica uma utilidade quando a estratégia de modo escuro está ativa. Por padrão, a variante segue prefers-color-scheme; com @custom-variant, ela pode seguir uma classe no elemento html. O botão de tema só faz sentido depois que você decide qual dessas duas fontes de verdade controla a página.

A ficha da clínica terá três preferências: claro, escuro e sistema. Três, não duas. Isso permite que a pessoa volte a acompanhar o sistema depois de escolher um tema manualmente.

O padrão segue o sistema operacional

Sem configuração adicional, classes dark:* viram media query.

html
<article class="bg-white text-slate-900 dark:bg-slate-950 dark:text-white">
  Consulta confirmada
</article>
css
@media (prefers-color-scheme: dark) {
  .dark\:bg-slate-950 { background-color: var(--color-slate-950); }
  .dark\:text-white { color: var(--color-white); }
}

Esse caminho é excelente quando não existe seletor manual. O navegador conhece a preferência antes do JavaScript e aplica a mídia durante a primeira pintura.

Uma custom variant entrega o controle a uma classe

Para um seletor manual, redefina a variante no CSS:

css
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));

Agora o mesmo HTML depende de .dark em um ancestral. O CSS medido ficou assim:

css
.dark\:bg-slate-950:where(.dark, .dark *) {
  background-color: var(--color-slate-950);
}

O uso de :where() mantém a especificidade baixa. Você não ganhou uma camada mais forte de cascata só porque ativou o tema.

Claro, escuro e sistema precisam de um estado explícito

Guarde a escolha como uma dessas três strings. Quando for sistema, remova a preferência local e consulte a mídia.

js
function escuroAtivo(preferencia) {
  if (preferencia === 'escuro') return true;
  if (preferencia === 'claro') return false;
  return matchMedia('(prefers-color-scheme: dark)').matches;
}
js
const preferencia = localStorage.getItem('tema') ?? 'sistema';
document.documentElement.classList.toggle('dark', escuroAtivo(preferencia));
document.documentElement.style.colorScheme = escuroAtivo(preferencia) ? 'dark' : 'light';

color-scheme avisa ao navegador como pintar controles nativos, barras de rolagem e campos. Sem isso, a página pode ficar escura enquanto um input mantém superfície clara inadequada.

O flash nasce antes do seu módulo principal

Se o script do aplicativo roda depois da primeira pintura, o HTML começa claro e troca para escuro. A correção técnica é aplicar a classe antes de carregar o CSS ou a aplicação, usando um trecho mínimo no head gerado pelo layout.

html
<script>
  const p = localStorage.getItem('tema') ?? 'sistema';
  const d = p === 'escuro' || (p === 'sistema' && matchMedia('(prefers-color-scheme: dark)').matches);
  document.documentElement.classList.toggle('dark', d);
</script>

O contrato deste blog proíbe script cru dentro de artigo publicado; o bloco é apenas código para o layout do seu projeto, não markup executado nesta página. Em sites com CSP, use nonce, hash ou uma política equivalente em vez de liberar script inline globalmente.

As duas estratégias quase não mudaram o bundle do teste

Compilei o mesmo cartão com preferência do sistema e com custom variant. Os arquivos minificados mediram 4.510 e 4.516 bytes, diferença de seis bytes.

bash
wc -c dark-system.out.css dark-class.out.css
4510 dark-system.out.css 4516 dark-class.out.css 9026 total

Escolha pela experiência, não por esses seis bytes. Sistema oferece simplicidade; classe oferece controle manual e exige persistência correta.

Tokens reduzem repetição de dark:

Repetir pares claro/escuro em toda caixa aumenta chance de esquecer borda ou texto secundário. Defina tokens semânticos em variáveis normais e conecte as classes a eles.

css
:root { --surface: oklch(98% 0.01 250); --ink: oklch(20% 0.03 250); }
.dark { --surface: oklch(18% 0.03 250); --ink: oklch(95% 0.01 250); }

@theme inline {
  --color-surface: var(--surface);
  --color-ink: var(--ink);
}
html
<article class="bg-surface text-ink">Consulta confirmada</article>

O @theme do Tailwind explica por que inline é importante quando um token aponta para variável que muda em runtime.

Escuro não é inverter duas cores

Sombras precisam ficar mais sutis, bordas precisam separar superfícies próximas e imagens podem exigir fundo próprio. Um logo preto transparente desaparece. Revise também estados: foco, hover, erro, disabled e seleção de texto.

html
<button class="bg-cyan-700 text-white focus-visible:outline-cyan-300
  dark:bg-cyan-500 dark:text-slate-950 dark:focus-visible:outline-white">
  Confirmar
</button>

Use a escala de cores do Tailwind para medir contraste; o mesmo passo numérico possui luminosidade diferente entre famílias.

O botão comunica a próxima preferência

O rótulo deve dizer a ação ou estado com clareza. Um ícone de lua sozinho é ambíguo e depende de conhecimento cultural. Se o controle percorre três estados, mostre “Tema: sistema”, “Tema: claro” ou “Tema: escuro”.

html
<button type="button" aria-label="Alterar tema; atual: sistema">
  Tema: sistema
</button>

Não use aria-pressed para um ciclo de três valores; ele modela booleano. Um grupo de três radios ou menu de escolha representa melhor a preferência.

Erro reproduzido: variante escura com nome inválido

darkk: não é um modo alternativo; é apenas um prefixo inexistente. O CLI 4.3.3 recebeu este caso mínimo:

css
@import "tailwindcss";
.qa { @apply darkk:bg-slate-950; }
bash
printf '@import "tailwindcss"; .qa { @apply darkk:bg-slate-950; }\n' | npx @tailwindcss/cli@4.3.3 -i - -o /dev/null
Error: Cannot apply utility class `darkk:bg-slate-950` because the `darkk` variant does not exist.

O processo encerrou com código 1. Corrija para dark: e então verifique se a estratégia do projeto segue o sistema ou a custom variant documentada.

Missão sem flash e sem perda de controle

Implemente os três estados, recarregue a página em cada um e confirme que a escolha permanece. Remova a preferência e altere o sistema para testar o modo automático. Navegue por teclado e verifique foco nos dois temas. A missão termina quando texto, borda e ação continuam distinguíveis sem depender apenas de cor. Depois reveja estados no Tailwind para testar o componente inteiro, não só o fundo.

Faça a auditoria por superfície

Liste fundo da página, cartão, área elevada, campo, menu e modal. Em cada linha, registre cor de fundo, texto principal, texto secundário, borda e foco nos dois temas. Essa matriz encontra o cartão que recebeu fundo escuro mas manteve borda clara demais, ou o texto secundário que desaparece em superfície elevada.

Revise imagens separadamente. Fotografia não precisa ser invertida; ilustração e logo podem exigir variante própria ou contorno. Um overlay calculado para fundo claro pode reduzir contraste no escuro. Carregue ambos os assets com dimensões iguais para evitar mudança de layout durante a troca.

Considere também a primeira resposta do servidor. Uma página pode renderizar tema do sistema sem JavaScript, mas uma preferência persistida no navegador só é conhecida no cliente. Se o produto autentica a escolha, cookie ou perfil pode permitir resposta coerente no servidor; isso muda privacidade e arquitetura e não deve ser inferido silenciosamente.

Por fim, teste em janela anônima, armazenamento indisponível e preferência do sistema alterada enquanto a página permanece aberta. “Sistema” precisa ouvir a mudança; “claro” e “escuro” manuais precisam permanecer estáveis. O guia da trilha posiciona esse trabalho depois dos estados porque tema é uma condição transversal, não apenas uma paleta alternativa.

  • tailwind
  • dark mode
  • tema
  • prefers-color-scheme
  • acessibilidade

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

Todo o código deste artigo foi executado em @tailwindcss/cli 4.3.3 no Node 26.3.0, e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. Tailwind CSS — Dark mode — tailwindcss.com
  2. MDN — prefers-color-scheme — developer.mozilla.org

Continue por aqui