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.
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.
<article class="bg-white text-slate-900 dark:bg-slate-950 dark:text-white">
Consulta confirmada
</article>@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:
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));Agora o mesmo HTML depende de .dark em um ancestral. O CSS medido ficou assim:
.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.
function escuroAtivo(preferencia) {
if (preferencia === 'escuro') return true;
if (preferencia === 'claro') return false;
return matchMedia('(prefers-color-scheme: dark)').matches;
}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.
<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.
wc -c dark-system.out.css dark-class.out.cssEscolha 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.
: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);
}<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.
<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”.
<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:
@import "tailwindcss";
.qa { @apply darkk:bg-slate-950; }printf '@import "tailwindcss"; .qa { @apply darkk:bg-slate-950; }\n' | npx @tailwindcss/cli@4.3.3 -i - -o /dev/nullO 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.
Dúvidas e comentários
Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.
Entrar para perguntarÉ o mesmo login gratuito dos cursos.
Nenhuma dúvida por aqui ainda — a primeira pode ser a sua.
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
- Tailwind CSS — Dark mode — tailwindcss.com
- MDN — prefers-color-scheme — developer.mozilla.org


