Destino Dev

Blog

Criar temas dark/light com CSS Layers, Nesting e custom properties — passo a passo

Por John Pires · 19 de março de 2026

Criar temas dark/light com CSS Layers, Nesting e custom properties — passo a passo

1) O que é e por que importa

O problema que estamos resolvendo

Aplicar temas (modo claro/escuro) em uma aplicação envolve trocar cores e garantir que componentes respeitem essas escolhas sem aumentos artificiais de especificidade. Tradicionalmente se usou classes com !important, duplicação de regras ou JS pesado. Aqui usamos três recursos modernos do CSS: custom properties, CSS Layers e CSS Nesting para organizar, sobrepor e escrever CSS de forma limpa.

Benefício prático

Com essa abordagem você terá: variáveis fáceis de trocar; um mecanismo nativo de prioridade entre blocos de estilos (layers) evitando !important; e sintaxe mais concisa com nesting. Resultado: temas robustos que não quebram ao escalar o projeto.

2) Conceitos fundamentais com analogias

Custom properties — a caixa de ferramentas

Pense nas custom properties (variáveis CSS) como caixas rotuladas com ferramentas: "--color-bg" ou "--color-text". Ao invés de ir trocando cor em cada componente, você troca o rótulo na raiz e todo mundo usa a nova ferramenta. Isso é essencial para temas.

CSS Layers — pilhas de folhas transparentes

Imagine folhas de acetato empilhadas: cada folha tem desenhos (regras CSS). As regras na folha superior falam mais alto se tiverem a mesma especificidade. @layer permite criar e ordenar essas folhas. Declarar uma camada de tema acima das outras garante que as cores do tema prevaleçam sem aumentar especificidade.

CSS Nesting — escrever como SCSS sem preprocessador

Nesting é simplesmente poder escrever regras aninhadas para componentes, reduzindo repetição. Em vez de repetir seletores longos, você aninha o estilo do elemento filho sob o bloco do pai usando &. É legível e evita erros de cópia e cola.

3) Passo a passo com código comentado

Estrutura mínima do HTML

Antes de mexer no CSS, precisamos de um HTML simples que suporte alternância de tema. Usaremos um atributo 'data-theme' na <html> para detectar qual tema está ativo. Isso é simples e funciona sem frameworks.

<!doctype html>
<html lang='pt-br' data-theme='light'>
  <head>
    <meta charset='utf-8'>
    <meta name='viewport' content='width=device-width,initial-scale=1'>
    <title>Exemplo de Tema</title>
    <link rel='stylesheet' href='styles.css'>
  </head>
  <body>
    <header class='topbar'>
      <h1>Meu App</h1>
      <button id='theme-toggle' aria-pressed='false'>Alternar tema</button>
    </header>

    <main class='container'>
      <section class='card'>
        <h2>Card de exemplo</h2>
        <p>Conteúdo do card que muda de cor conforme o tema.</p>
      </section>
    </main>

    <script src='theme-toggle.js'></script>
  </body>
</html>

Contexto: esse HTML oferece o esqueleto para aplicarmos variáveis e layers. O botão alterna o atributo 'data-theme' no elemento root; o CSS vai reagir a isso. Depois do bloco, vamos ver o CSS que define layers, variáveis e componentes.

Definindo layers e variáveis — arquivo styles.css

Agora criamos três layers: base (reset e tipografia), components (estilos padrão dos componentes) e theme (variáveis que sobrescrevem padrões). A ordem importa: declaramos a ordem das layers para controlar quem vence na cascata.

/* Order: base, components, theme */
@layer base, components, theme;

/* --- base layer: reset e utilidades --- */
@layer base {
  :root {
    /* variáveis padrão (fallbacks) */
    --color-bg: 255 255 255; /* RGB sem alpha */
    --color-text: 0 0 0;
    --radius: 8px;
    --gap: 16px;
  }

  *, *::before, *::after {
    box-sizing: border-box;
  }

  body {
    margin: 0;
    font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial;
    background-color: rgb(var(--color-bg));
    color: rgb(var(--color-text));
    line-height: 1.4;
  }
}

/* Contexto: aqui definimos a base e variáveis padrão. */

O que acontece: definimos variáveis com valores RGB sem alpha (para facilitar uso com color-mix e controlar contraste). A propriedade background-color e color usam essas variáveis; mudar as variáveis muda a aparência global.

Componentes com nesting e layer components

Em seguida, criamos estilos de componentes no layer components. Usamos nesting para manter a estrutura clara. Observe como não precisamos colocar classes repetidas para elementos internos.

@layer components {
  .topbar {
    padding: var(--gap);
    display: flex;
    gap: var(--gap);
    align-items: center;
    justify-content: space-between;
    background: rgb(var(--color-bg));
    border-bottom: 1px solid rgba(0,0,0,0.06);

    & h1 {
      font-size: 1rem;
      margin: 0;
    }

    & button {
      padding: 8px 12px;
      border-radius: 6px;
      border: 1px solid rgba(0,0,0,0.08);
      background: color-mix(in srgb, rgb(var(--color-text)) 8%, transparent);
      color: rgb(var(--color-text));
      cursor: pointer;
    }
  }

  .container {
    padding: calc(var(--gap) * 1.5);
  }

  .card {
    background: rgb(var(--color-bg));
    border-radius: var(--radius);
    padding: var(--gap);
    box-shadow: 0 6px 18px rgba(0,0,0,0.06);

    & h2 { margin-top: 0; }
  }
}

/* Contexto: layer components define estilos reutilizáveis que usam as custom properties da root. */

O que acontece: os componentes usam as variáveis definidas em :root. Se o layer theme sobrescrever as variáveis, esses componentes já atualizarão sem precisar alterar suas regras.

Layer theme — variáveis por tema

Agora a parte central: colocamos a mudança de variáveis dentro do layer theme. Como declaramos a ordem de layers com theme por último, essas variáveis têm precedência sobre as declaradas antes — sem aumentar especificidade.

@layer theme {
  /* Tema claro padrão */
  html[data-theme='light'] {
    --color-bg: 255 255 255;
    --color-text: 17 24 39; /* um tom escuro */
  }

  /* Tema escuro */
  html[data-theme='dark'] {
    --color-bg: 12 14 18;
    --color-text: 229 231 235;
  }
}

/* Contexto: trocar o atributo data-theme basta para alterar variáveis e, por consequência, todo o visual. */

O que acontece: quando o HTML recebe data-theme='dark', as variáveis do theme layer entram em vigor e todo o CSS que depende delas muda. Não usamos classes por componente nem !important.

4) Exemplo prático completo

Arquivo JavaScript para alternância

Precisamos de um pequeno script para alternar o atributo data-theme e armazenar a preferência no localStorage. Esse JS é direto e não manipula estilos — apenas o atributo, deixando o CSS fazer o resto.

// theme-toggle.js
(function () {
  const root = document.documentElement;
  const btn = document.getElementById('theme-toggle');

  function getStoredTheme() {
    return localStorage.getItem('theme');
  }

  function applyTheme(t) {
    root.setAttribute('data-theme', t);
    btn.setAttribute('aria-pressed', String(t === 'dark'));
  }

  // inicializa com preferencia armazenada ou 'light'
  const initial = getStoredTheme() || 'light';
  applyTheme(initial);

  btn.addEventListener('click', () => {
    const next = root.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
    applyTheme(next);
    localStorage.setItem('theme', next);
  });
})();

Contexto: o script lê e grava no localStorage e alterna o atributo data-theme. O CSS observa esse atributo e aplica as variáveis do layer theme.

Como tudo funciona junto

Fluxo final: o navegador carrega styles.css. As layers são registradas e ordenadas. :root define defaults; components usam essas variáveis; theme, sendo a camada mais alta, sobrescreve variáveis quando html[data-theme] mudar. Nenhuma regra ganha especificidade extra, então a manutenção é previsível.

Teste e resultado esperado

Com o HTML, CSS e JS descritos, ao clicar no botão a página troca entre um fundo claro com texto escuro e um fundo escuro com texto claro. Componentes (topbar, card) se adaptam automaticamente.

5) Erros comuns, armadilhas e dicas práticas

Erro: declarar layers na ordem errada

Se você declarar @layer base, theme, components, o layer components terá precedência sobre theme quando a especificidade for igual. Isso quebra o propósito do theme. Sempre declare a ordem intencionalmente, por exemplo: @layer base, components, theme para que theme prevaleça.

Erro: misturar variáveis com valores de componente diretamente

Evite definir cores fixas dentro de componentes quando está usando variáveis. Se colocar color: #222 direto no .card, trocar a variável não afetará o componente. Use sempre var(--color-text) ou rgb(var(--color-text)).

Dica: usar RGB para facilitar manipulações

Armazenar valores como '255 255 255' permite usar funções como color-mix ou rgba facilmente: background: rgb(var(--color-bg)); ou background: color-mix(in srgb, rgb(var(--color-bg)) 90%, black 10%);

Prática: mantenha as definições de cor por tema apenas dentro do layer theme; evite duplicar variáveis em cada componente.

6) Próximos passos para aprofundar

Adicionar preferência do sistema (prefers-color-scheme)

Você pode inicializar o tema com base na preferência do usuário: usar matchMedia('(prefers-color-scheme: dark)') para escolher entre 'dark' e 'light' quando não houver preferências salvas. Combine isso com a lógica de armazenamento que mostramos.

Unir com container queries e tokens design

Depois de dominar layers e custom properties, avance para design tokens (conjunto central de variáveis) e use container queries para ajustar estilos do componente com base no espaço disponível. Variáveis tornam a integração mais simples.

Automatizar com build tools quando necessário

Embora as features usadas aqui sejam nativas, alguns times preferem compilar para suporte mais antigo ou extrair tokens. Use PostCSS ou ferramentas de design tokens se precisar gerar versões para browsers legados.

Conclusão prática

Usar custom properties, CSS Layers e CSS Nesting juntos facilita criar temas escaláveis: variáveis para trocar a aparência, layers para controlar precedência sem !important, e nesting para código mais limpo. O padrão que mostramos — ordenação de layers, variáveis por tema, componentes consumindo variáveis — é aplicável em projetos reais.

Resumo operacional:

  • Defina @layer base, components, theme na ordem desejada.
  • Coloque defaults em :root no layer base.
  • Crie regras de componentes que usem var(--...).
  • Coloque variáveis específicas de tema no layer theme usando html[data-theme='...'].
  • Use JS mínimo para alternar atributo data-theme.

Seguindo esses passos você terá um sistema de theming previsível, fácil de manter e que escala com novos componentes sem quebrar a cascata do CSS. Experimente adicionar tokens para espaçamentos, sombras e tipografia e observe como a estratégia se mantém consistente.

Material para estudo adicional: documentação oficial de CSS Cascade Layers, especificação de CSS Nesting e artigos sobre custom properties e color-mix. Pratique migrando um pequeno conjunto de componentes de um projeto existente para esse padrão.

Assuntos: CSS, themes, layers, custom-properties

Leia também