Detectando o tema do sistema com prefers-color-scheme
Quando o sistema operacional está em modo escuro, ele expõe essa preferência para os aplicativos — incluindo o browser. O browser, por sua vez, repassa o valor para o CSS e o JavaScript através da media feature prefers-color-scheme. Isso permite que um site escolha o tema certo já na primeira renderização, sem esperar o usuário tocar em nenhum botão.
A preferência vem do OS — macOS em "Aparência", Windows em "Personalização > Cores" — e o browser apenas relê esse valor.
A media query no CSS
@media (prefers-color-scheme: dark) {
body {
background: #1a1a1a;
color: #f0f0f0;
}
}Quando o sistema está em modo escuro, esses estilos entram. Quando o usuário troca o tema do sistema com a página aberta, o CSS reaplica em tempo real, sem reload.
A spec define três valores: light, dark e no-preference. Na prática, browsers atuais sempre reportam light ou dark — no-preference virou letra morta. Não vale escrever regras para ele.
Lendo o valor no JavaScript
Para reagir no código — por exemplo, num componente React que precisa do tema antes da primeira renderização — a entrada é window.matchMedia:
const mq = window.matchMedia('(prefers-color-scheme: dark)')
console.log(mq.matches) // true se o sistema estiver em modo escuromatchMedia devolve um MediaQueryList. .matches é o booleano atual; addEventListener('change', ...) permite ouvir mudanças enquanto a página está aberta:
mq.addEventListener('change', (event) => {
const novo = event.matches ? 'dark' : 'light'
console.log('Sistema mudou para:', novo)
})O blog não escuta esse evento de propósito: se o usuário já escolheu um tema manualmente, faz mais sentido respeitar essa escolha do que sobrescrever no meio da leitura.
Em Chrome DevTools > Rendering, dá para forçar dark ou light sem precisar mudar o tema do sistema — útil para testar.
Como o blog combina com localStorage
A leitura do tema acontece uma única vez, na montagem, e mistura duas fontes:
useEffect(() => {
const stored = localStorage.getItem('theme') as Theme | null
const preferred = window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light'
const resolved = stored ?? preferred
setTheme(resolved)
document.documentElement.classList.toggle('dark', resolved === 'dark')
}, [])A escolha explícita do usuário (no localStorage) vence; a preferência do sistema é o fallback quando ele nunca tocou no botão. A parte de persistência tem um post separado sobre localStorage; aqui interessa só a metade matchMedia.
Por que isso importa
Sem prefers-color-scheme, todo site abre num tema fixo — geralmente claro, porque a maioria dos designs ainda parte daí. Um usuário que prefere escuro precisa clicar no botão toda vez que entra num site novo. Ler a preferência do sistema elimina esse passo: a interface já chega no estado esperado.