Dark mode: implementação técnica e considerações de design

1. Fundamentos do dark mode e motivações de design

1.1. O que é dark mode

Dark mode, ou modo escuro, é uma variante de interface que utiliza cores claras sobre fundos escuros, invertendo a paleta tradicional de texto escuro sobre fundo claro. Historicamente, os primeiros monitores usavam fósforo verde sobre fundo preto, mas com a popularização das GUIs, o modo claro tornou-se padrão. O ressurgimento do dark mode ocorreu com sistemas operacionais modernos — macOS Mojave (2018), Windows 10 (2019) e Android 10 — e rapidamente se consolidou como recurso essencial em aplicações web.

1.2. Benefícios para o usuário

A redução da fadiga ocular é o benefício mais citado, especialmente em ambientes com pouca luz. Em telas OLED e AMOLED, pixels pretos consomem menos energia, gerando economia de bateria significativa — estudos indicam redução de até 30% no consumo. Para usuários com sensibilidade à luz ou certas condições visuais, o dark mode pode melhorar a legibilidade e reduzir o ofuscamento.

1.3. Desafios de design

O dark mode exige repensar contrastes e hierarquia visual. Texto branco puro (#FFFFFF) sobre fundo preto (#000000) causa ofuscamento e fadiga. Cores saturadas perdem vibração em fundos escuros. Elementos de sombra precisam ser substituídos por brilho (glow) ou sobreposições translúcidas. A legibilidade de textos longos pode ser prejudicada, tornando o modo claro mais adequado para leitura extensa.

2. Estratégias de implementação com CSS custom properties

2.1. Definição de variáveis CSS

As custom properties permitem centralizar a paleta de cores e alternar temas com eficiência:

:root {
  --color-bg: #FFFFFF;
  --color-text: #1A1A1A;
  --color-primary: #0066CC;
  --color-secondary: #6B7280;
  --color-border: #E5E7EB;
  --color-surface: #F3F4F6;
  --color-shadow: rgba(0, 0, 0, 0.1);
}

[data-theme="dark"] {
  --color-bg: #121212;
  --color-text: #E5E7EB;
  --color-primary: #3B82F6;
  --color-secondary: #9CA3AF;
  --color-border: #374151;
  --color-surface: #1F2937;
  --color-shadow: rgba(0, 0, 0, 0.5);
}

2.2. Media query prefers-color-scheme

Para detecção automática da preferência do sistema:

@media (prefers-color-scheme: dark) {
  :root {
    --color-bg: #121212;
    --color-text: #E5E7EB;
    --color-primary: #3B82F6;
    --color-secondary: #9CA3AF;
    --color-border: #374151;
    --color-surface: #1F2937;
    --color-shadow: rgba(0, 0, 0, 0.5);
  }
}

2.3. Classes de tema para alternância manual

.theme-light {
  --color-bg: #FFFFFF;
  --color-text: #1A1A1A;
  --color-primary: #0066CC;
}

.theme-dark {
  --color-bg: #121212;
  --color-text: #E5E7EB;
  --color-primary: #3B82F6;
}

3. Alternância dinâmica com JavaScript e armazenamento de preferência

3.1. Script de toggle

const toggleTheme = () => {
  const html = document.documentElement;
  const currentTheme = html.getAttribute('data-theme');
  const newTheme = currentTheme === 'dark' ? 'light' : 'dark';

  html.setAttribute('data-theme', newTheme);
  localStorage.setItem('theme', newTheme);
};

3.2. Persistência com localStorage

const applySavedTheme = () => {
  const savedTheme = localStorage.getItem('theme');
  const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;

  if (savedTheme) {
    document.documentElement.setAttribute('data-theme', savedTheme);
  } else if (systemPrefersDark) {
    document.documentElement.setAttribute('data-theme', 'dark');
  }
};

applySavedTheme();

3.3. Tratamento de conflitos

A preferência manual do usuário deve sobrescrever a do sistema. Implemente um observador para mudanças no sistema:

window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
  if (!localStorage.getItem('theme')) {
    document.documentElement.setAttribute('data-theme', e.matches ? 'dark' : 'light');
  }
});

4. Transições suaves e animações entre temas

4.1. Transições CSS

*, *::before, *::after {
  transition: background-color 0.3s ease,
              color 0.3s ease,
              border-color 0.3s ease,
              box-shadow 0.3s ease;
}

4.2. Técnicas de cross-fade

Para transições mais elaboradas, use mix-blend-mode:

.theme-transitioning {
  animation: themeFade 0.4s ease;
}

@keyframes themeFade {
  0% { opacity: 0.8; }
  100% { opacity: 1; }
}

4.3. Performance

Evite repaints excessivos com will-change:

body {
  will-change: background-color, color;
  contain: layout style paint;
}

5. Considerações de acessibilidade e contraste

5.1. Diretrizes WCAG 2.1

O contraste mínimo deve ser 4.5:1 para texto normal e 3:1 para texto grande (acima de 18px ou 14px bold). Ferramentas como WebAIM Contrast Checker ajudam a validar combinações.

5.2. Cuidados com cores

Evite azul puro (#0000FF) em fundos escuros — causa ofuscamento. Prefira versões com saturação reduzida e maior luminosidade. Teste com simuladores de daltonismo (protanopia, deuteranopia, tritanopia).

5.3. Suporte a preferências do usuário

@media (prefers-contrast: more) {
  :root[data-theme="dark"] {
    --color-text: #FFFFFF;
    --color-bg: #000000;
  }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
  }
}

6. Tratamento de imagens, ícones e mídia no dark mode

6.1. Filtros CSS para imagens

[data-theme="dark"] img {
  filter: brightness(0.8) contrast(1.2);
}

[data-theme="dark"] .invert-on-dark {
  filter: invert(1) hue-rotate(180deg);
}

6.2. Imagens alternativas com picture

<picture>
  <source srcset="logo-dark.svg" media="(prefers-color-scheme: dark)">
  <img src="logo-light.svg" alt="Logo">
</picture>

6.3. Ícones SVG com variáveis

.icon {
  fill: var(--color-text);
  stroke: var(--color-border);
}

.icon-primary {
  fill: var(--color-primary);
}

7. Testes, manutenção e extensão para múltiplos temas

7.1. Ferramentas de teste

As DevTools do Chrome/Edge permitem emular prefers-color-scheme no painel de Rendering. Extensões como "WCAG Color Contrast Checker" e "Axe DevTools" ajudam na validação de acessibilidade.

7.2. Manutenção com tokens

Organize variáveis em um arquivo _tokens.css:

/* _tokens.css */
:root {
  --color-bg-light: #FFFFFF;
  --color-bg-dark: #121212;
  --color-text-light: #1A1A1A;
  --color-text-dark: #E5E7EB;
  /* ... mais tokens */
}

7.3. Arquitetura para múltiplos temas

[data-theme="dark"] { /* paleta escura */ }
[data-theme="light"] { /* paleta clara */ }
[data-theme="sepia"] { /* paleta sépia */ }
[data-theme="high-contrast"] { /* alto contraste */ }

Referências