Voltar ao blog
dark modethemingdesign tokensCSSacessibilidadefront-end

Dark mode e theming: implementando temas sem duplicar CSS

2027-01-07
Dark mode e theming: implementando temas sem duplicar CSS

Dark mode deixou de ser um luxo e virou expectativa. Usuários passam horas diante de telas e muitos preferem, ou precisam, de uma interface escura para reduzir o cansaço visual, economizar bateria em telas OLED ou simplesmente por gosto. O problema é como implementar temas do jeito certo. A abordagem ingênua, escrever o CSS duas vezes (uma para claro, outra para escuro), cria um pesadelo de manutenção onde cada mudança precisa ser feita em dois lugares e as inconsistências se multiplicam. Fazer theming bem é, antes de tudo, uma questão de arquitetura de CSS.

Este guia é sobre a mecânica do theming, não sobre governança de componentes (o papel de um design system em manter a biblioteca coerente é tema de outro post). Aqui o foco é concreto: como estruturar cores com custom properties e tokens semânticos, respeitar a preferência do sistema, garantir contraste no escuro, adaptar imagens e sombras, e persistir a escolha do usuário sem aquele flash desagradável de tema errado.

O erro de duplicar CSS

Antes da solução, vale entender por que a abordagem óbvia falha. Duplicar estilos para cada tema significa que a cor de um botão está definida em dois blocos diferentes. Quando o designer muda o tom da marca, alguém precisa lembrar de atualizar os dois, e inevitavelmente esquece um. Multiplique isso por centenas de elementos e dois, três ou mais temas, e o CSS vira uma teia impossível de manter coerente. A raiz do problema é que os valores de cor estão espalhados e repetidos, em vez de centralizados.

A solução moderna inverte isso: em vez de duplicar estilos, você define os valores uma vez, em variáveis, e troca apenas os valores entre temas. O CSS que usa as cores nunca muda; só muda o que as variáveis apontam.

CSS custom properties: a fundação

A peça técnica que torna tudo possível são as CSS custom properties (as variáveis nativas do CSS). Elas permitem declarar um valor uma vez e referenciá-lo em qualquer lugar. Para theming, a estratégia é definir as cores no elemento raiz e sobrescrevê-las conforme o tema ativo:

:root {
  --cor-fundo: #ffffff;
  --cor-texto: #1a1a1a;
  --cor-superficie: #f4f4f5;
}

:root[data-theme="dark"] {
  --cor-fundo: #0f0f10;
  --cor-texto: #f4f4f5;
  --cor-superficie: #1c1c1f;
}

/* o componente usa a variável, nunca a cor direta */
.card {
  background: var(--cor-superficie);
  color: var(--cor-texto);
}

O ponto genial é que .card não sabe nem se importa com o tema. Ele usa var(--cor-superficie), e o valor certo é resolvido conforme o data-theme no elemento raiz. Trocar de tema é trocar um atributo, e a interface inteira responde. Nenhuma regra de componente é duplicada.

Tokens semânticos: nomeie o papel, não a cor

Aqui está a distinção que separa um theming frágil de um robusto. É tentador nomear as variáveis pela cor: --azul, --cinza-claro. Mas isso quebra no dark mode, onde o "cinza-claro" precisa virar um cinza escuro, e aí o nome mente. A solução são os tokens semânticos: nomear a variável pelo seu papel, não pela sua aparência.

  • Em vez de --branco, use --cor-fundo.
  • Em vez de --cinza-escuro, use --cor-texto.
  • Em vez de --azul-500, use --cor-primaria.

O nome descreve a função ("o fundo da página"), e o valor muda por tema sem que o nome deixe de fazer sentido. No tema claro, --cor-fundo é branco; no escuro, é quase preto; o nome continua correto. A boa prática combina duas camadas: uma paleta de tokens primitivos com as cores cruas (--azul-500), e uma camada de tokens semânticos que aponta para eles conforme o tema. O componente sempre usa o semântico.

Respeite a preferência do sistema

O usuário já disse ao sistema operacional se prefere claro ou escuro. A interface deveria começar respeitando isso. O CSS oferece a media query prefers-color-scheme exatamente para ler essa preferência:

@media (prefers-color-scheme: dark) {
  :root {
    --cor-fundo: #0f0f10;
    --cor-texto: #f4f4f5;
  }
}

O padrão ideal combina a preferência do sistema com a escolha explícita: por padrão, siga o prefers-color-scheme; se o usuário escolher um tema manualmente na interface, essa escolha vence e é lembrada. Assim você honra a preferência automática sem tirar o controle de quem quer decidir.

Contraste no escuro: não é só inverter

Um erro comum é achar que dark mode é apenas trocar branco por preto e vice-versa. Não é. O tema escuro tem regras próprias de legibilidade e conforto:

  • Evite preto puro e branco puro. Texto branco puro (#fff) sobre fundo preto puro (#000) gera um contraste tão duro que causa halação (o texto parece vibrar). Prefira um quase-preto para o fundo e um quase-branco para o texto.
  • Mantenha o contraste acessível. As mesmas exigências de contraste mínimo entre texto e fundo valem no escuro. Um cinza claro demais sobre fundo escuro pode falhar tanto quanto no tema claro.
  • Reduza a saturação das cores de destaque. Cores muito vivas "brilham" demais sobre fundo escuro e cansam. Versões um pouco menos saturadas funcionam melhor no escuro.
  • Use elevação por luz, não por sombra. No tema claro, superfícies "acima" ganham sombra; no escuro, sombras somem no fundo. A convenção é indicar elevação clareando levemente a superfície.

Imagens, sombras e outros detalhes

Além das cores de interface, alguns elementos precisam de atenção especial na troca de tema:

Elemento Cuidado no dark mode
Sombras Sombras escuras somem no fundo escuro; use clareamento de superfície para dar profundidade
Ilustrações e logos Versões com fundo transparente e cores que funcionem nos dois temas, ou uma variante por tema
Fotos Considere reduzir levemente o brilho de imagens muito claras para não ofuscar
Bordas Bordas sutis ajudam a separar superfícies quando as sombras deixam de funcionar

Uma técnica útil é usar a própria media query ou o atributo de tema para trocar a fonte de uma imagem quando necessário, garantindo que ilustrações com fundo branco não apareçam como um bloco brilhante no escuro.

Persista a escolha e evite o flash

O detalhe que separa uma implementação amadora de uma polida é o FART (flash of incorrect theme), aquele piscar em que a página carrega no tema errado por um instante antes de corrigir. Ele acontece quando a preferência do usuário é lida tarde demais, depois de a página já ter pintado no tema padrão.

A solução tem duas partes:

  • Persistir a escolha do usuário (tipicamente no armazenamento local do navegador), para lembrá-la nas próximas visitas.
  • Aplicar o tema o mais cedo possível, antes de a página renderizar, definindo o atributo data-theme no elemento raiz logo no início do carregamento, com base na escolha salva ou na preferência do sistema. Aplicado cedo, o tema correto já é o primeiro a aparecer, sem flash.

Esse cuidado é pequeno em código e enorme em percepção de qualidade: uma interface que já abre no tema certo passa uma sensação de solidez que a que pisca nunca alcança.

Temas como valores, não como duplicação

Fazer dark mode e theming corretamente é, no fundo, uma questão de arquitetura: centralizar as decisões de cor em tokens semânticos, expostos como custom properties, e trocar apenas os valores entre temas. Com essa base, adicionar um novo tema (escuro, alto contraste, uma marca alternativa) deixa de ser reescrever CSS e passa a ser definir um novo conjunto de valores. Some a isso o respeito à preferência do sistema, o cuidado com contraste e elevação no escuro, a adaptação de imagens e sombras e a persistência sem flash, e você tem um theming que escala em vez de virar dívida técnica.

Na ConstructorTI, implementamos theming e dark mode de forma sustentável: tokens semânticos sobre custom properties, respeito ao prefers-color-scheme com escolha do usuário, contraste acessível nos dois temas e persistência sem flash de tema errado. Se o seu produto precisa de dark mode, de múltiplas marcas ou de um sistema de temas que não vire pesadelo de manutenção, fale com o nosso time e saia da conversa com um plano claro de arquitetura de theming.