O problema do toggle tri-estado

Você abre o site, e lá está: um seletor de tema com Claro, Escuro e Sistema. Parece caprichado. Parece que respeita a escolha do usuário. Mas, na real, ele está competindo com o que o visitante realmente quer fazer.

Pensa comigo: por que alguém cai na sua página? Quer ler um artigo, comprar algo, resolver um problema. O toggle de tema é tangencial a isso. Cada opção extra num elemento persistente de UI é uma micro-decisão que ninguém pediu. E a dissonância cognitiva aparece: "Espera, qual a diferença entre Escuro e Sistema? O Sistema segue meu SO? Qual tá ativo agora?"

A discussão levantada por Lea Verou no CSS-Tricks resume bem: um controle de dois estados consegue expressar os três — desde que você aplique o override na primeira vez que ele fizer diferença.

Web developer inspecting light and dark mode toggle UI in browser devtools Software Concept Art

O padrão de dois estados na prática

A ideia é simples: comece seguindo o prefers-color-scheme do sistema e só grave um override no localStorage quando o usuário clicar no toggle. Sem botão "Sistema".

// theme.js — toggle de dois estados com fallback do sistema
const root = document.documentElement;
const stored = localStorage.getItem('theme'); // 'light' | 'dark' | null

// 1. Resolve o tema inicial: override salvo > preferência do sistema
const sistemaPrefereEscuro = window.matchMedia('(prefers-color-scheme: dark)').matches;
const temaInicial = stored ?? (sistemaPrefereEscuro ? 'dark' : 'light');
root.dataset.theme = temaInicial;

// 2. Mostra só a opção *oposta* na UI
function renderToggle() {
  const atual = root.dataset.theme;
  // Se está claro, oferece "Escuro"; se escuro, oferece "Claro".
  document.getElementById('theme-toggle').textContent =
    atual === 'dark' ? '☀️ Claro' : '🌙 Escuro';
}

// 3. No clique: inverte, persiste, re-renderiza
document.getElementById('theme-toggle').addEventListener('click', () => {
  const proximo = root.dataset.theme === 'dark' ? 'light' : 'dark';
  root.dataset.theme = proximo;
  localStorage.setItem('theme', proximo); // override salvo
  renderToggle();
});

// 4. Reage a mudanças do SO SÓ se não houver override
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
  if (localStorage.getItem('theme')) return; // usuário já escolheu
  root.dataset.theme = e.matches ? 'dark' : 'light';
  renderToggle();
});

renderToggle();
/* style.css — variáveis CSS controladas pelo atributo data-theme */
:root[data-theme="light"] {
  --bg: #ffffff;
  --fg: #111111;
}
:root[data-theme="dark"] {
  --bg: #111111;
  --fg: #f5f5f5;
}

body {
  background: var(--bg);
  color: var(--fg);
  transition: background 0.2s ease, color 0.2s ease;
}

O pulo do gato tá no passo 4: mudanças do sistema propagam só até o usuário fazer uma escolha. Depois disso, o localStorage manda. E o melhor: o visitante nunca vê um botão "Sistema", porque "Sistema" não é um estado que ele precisa gerenciar — é o fallback natural.

UI designer sketching two-state theme toggle wireframe on tablet IT Technology Image

Quando três estados realmente valem a pena

Calma, não sai deletando todo toggle tri-estado do seu codebase. O padrão de dois estados vale para toggles persistentes e sempre visíveis — aqueles no header que brigam com o objetivo principal do visitante. Três estados fazem sentido em dois cenários específicos:

1. O toggle mora num painel de configurações

Se o controle de tema está enterrado numa página de Preferências — onde o usuário chegou de propósito — o custo cognitivo da terceira opção cai pra quase zero. Nesse contexto, radios explícitos de Claro/Escuro/Sistema são até mais claros que um flip esperto de dois estados.

2. Você oferece mais de dois esquemas de cor

Se o site entrega Solarized, Nord, Dracula e um modo de alto contraste junto com o padrão do sistema, "Sistema" vira uma quinta opção real em vez de uma terceira redundante. O padrão de dois estados assume exatamente dois esquemas.

Cuidado: o caso de borda do toggle de volta

O que acontece se o usuário começa em Sistema (Claro), vai pra Escuro e volta? Ele cai em Sistema de novo ou num override Claro? A resposta honesta: tanto faz. Como a Lea aponta, a divergência só fica observável quando o SO troca pra escuro depois — e, nesse momento, corrigir é um clique. Não superengenheire uma máquina de estados pra um controle que o visitante mal nota.

A alternativa real: nenhum toggle

Se o design do seu site segura bem nos dois esquemas, considere entregar zero toggles e confiar 100% no prefers-color-scheme. O navegador já expõe isso nas configurações do SO. Um toggle é uma feature pra manter, testar em vários browsers e explicar. Às vezes o melhor UX é aquele que você não constrói.

Laptop screen showing CSS prefers-color-scheme media query code editor Dev Environment Setup

Resumo da ópera

Menos UI fazendo o mesmo que mais UI é quase sempre a escolha certa — principalmente pra controles que ficam entre o visitante e o objetivo dele. O padrão de dois estados te dá:

  • Controle total do usuário (override persistido no localStorage)
  • Preferência do sistema respeitada (via prefers-color-scheme)
  • Um botão a menos poluindo sua navegação

Se você mantém uma biblioteca de componentes ou design system, coloque isso como padrão. Reserve toggles tri-estado pra páginas de configuração dedicadas e temas multi-esquema. E se você curte ver como tooling moderno de IA on-device está sendo plugado em apps mobile, o tutorial de ADK para Kotlin & Android é uma ótima próxima leitura — o mesmo princípio de "menos superfície, mais clareza" vale pra UIs de agentes. Pra times que deployam infra de inferência por trás dessas experiências, o guia do SageMaker HyperPod inference operator cobre o lado backend.

Próximos passos: audite o header do seu site atrás de controles redundantes, rode um passe rápido de acessibilidade no seu prefers-color-scheme (ele respeita prefers-contrast?) e confira se seu toggle é anunciado corretamente pra leitores de tela via aria-pressed.

Este conteúdo foi elaborado com o auxílio de ferramentas de IA, com base em fontes confiáveis, e revisado pela nossa equipe editorial antes da publicação. Não substitui o aconselhamento de um profissional especializado.