CSS moderno sem framework: container queries, layers e nesting

1. Introdução ao CSS moderno sem dependências

Em 2025, o ecossistema CSS nativo atingiu um nível de maturidade que torna questionável a dependência exclusiva de frameworks como Bootstrap ou Tailwind. Três recursos — container queries, layers e nesting — resolvem problemas históricos de responsividade, especificidade e organização de código sem a necessidade de bibliotecas externas.

Container queries permitem que componentes respondam ao espaço disponível do seu container pai, não apenas à viewport. Layers oferecem controle explícito sobre a cascata, eliminando guerras de especificidade. Nesting nativo traz a sintaxe de aninhamento que antes exigia Sass ou Less. Juntos, formam um trio poderoso para escrever CSS modular, performático e de fácil manutenção.

Todos os navegadores modernos suportam esses recursos desde 2023-2024, com cobertura acima de 95% globalmente. Você pode usá-los em produção hoje.

2. Container Queries: design responsivo por componente

O conceito central das container queries é inverter a lógica de responsividade. Em vez de perguntar "qual o tamanho da tela?", perguntamos "qual o tamanho do container pai?". Isso permite que um mesmo componente se adapte a diferentes contextos de layout.

.card-container {
  container-type: inline-size;
  container-name: card;
}

@container card (min-width: 400px) {
  .card {
    display: grid;
    grid-template-columns: 200px 1fr;
  }
}

@container card (max-width: 399px) {
  .card {
    display: flex;
    flex-direction: column;
  }
}

A propriedade container-type define o eixo de dimensionamento (inline-size para largura, size para ambos os eixos). container-name nomeia o container para consultas específicas.

3. Trabalhando com container queries na prática

Um cenário comum é uma grid de produtos onde cada card precisa se reorganizar conforme o espaço disponível no container, não na viewport.

.products-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 1rem;
}

.product-card {
  container-type: inline-size;
  container-name: product;
  padding: 1rem;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
}

@container product (min-width: 300px) {
  .product-card {
    display: grid;
    grid-template-columns: 120px 1fr;
    gap: 1rem;
  }

  .product-image {
    width: 100%;
    height: auto;
  }

  .product-details {
    display: flex;
    flex-direction: column;
    justify-content: center;
  }
}

@container product (max-width: 299px) {
  .product-card {
    display: flex;
    flex-direction: column;
    text-align: center;
  }

  .product-image {
    max-width: 150px;
    margin: 0 auto;
  }
}

Combinar container queries com media queries é estratégia inteligente: use media queries para o layout macro (grid) e container queries para o micro-layout dos componentes.

4. CSS Layers: organização e controle de especificidade

Layers resolvem o problema clássico de "quem ganha na cascata". Com @layer, você define a ordem de precedência explicitamente, independentemente da ordem de importação ou especificidade dos seletores.

@layer reset, base, components, utilities;

@layer reset {
  *,
  *::before,
  *::after {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
  }
}

@layer base {
  body {
    font-family: system-ui, sans-serif;
    line-height: 1.6;
    color: #333;
  }

  h1, h2, h3 {
    line-height: 1.2;
    color: #111;
  }
}

@layer components {
  .button {
    padding: 0.5rem 1rem;
    border: none;
    border-radius: 4px;
    background: #0066cc;
    color: white;
    cursor: pointer;
  }
}

@layer utilities {
  .mt-4 { margin-top: 1rem; }
  .text-center { text-align: center; }
  .bg-gray { background: #f5f5f5; }
}

A ordem declarada em @layer reset, base, components, utilities define que reset tem menor prioridade e utilities a maior. Isso significa que uma classe utilitária sempre sobrescreverá um estilo de componente, mesmo que a especificidade seja idêntica.

5. Estratégias avançadas com layers

Para projetos maiores, você pode importar arquivos dentro de layers, mantendo a organização modular.

/* main.css */
@layer base, layout, components, utilities;

@layer base {
  @import 'reset.css';
  @import 'typography.css';
}

@layer layout {
  @import 'grid.css';
  @import 'header.css';
  @import 'footer.css';
}

@layer components {
  @import 'buttons.css';
  @import 'cards.css';
  @import 'forms.css';
}

@layer utilities {
  @import 'spacing.css';
  @import 'colors.css';
}

Layers podem ser aninhados para controle ainda mais granular:

@layer base {
  @layer reset, tokens, typography;

  @layer reset {
    /* estilos de reset */
  }

  @layer tokens {
    /* variáveis CSS */
  }
}

A grande vantagem: você elimina a necessidade de convenções como BEM ou especificidade artificial para garantir que utilitários sobrescrevam componentes.

6. Nesting nativo: aninhamento CSS sem pré-processadores

O nesting nativo do CSS segue a especificação do W3C e é mais flexível que o Sass. A sintaxe básica usa & para referenciar o seletor pai, mas em muitos casos é opcional.

.card {
  padding: 1rem;
  border-radius: 8px;
  background: white;

  /* Aninhamento sem & para seletores descendentes */
  .card-title {
    font-size: 1.25rem;
    font-weight: 600;
    margin-bottom: 0.5rem;
  }

  .card-body {
    color: #666;
    line-height: 1.5;
  }

  /* Aninhamento com & para pseudo-classes */
  &:hover {
    box-shadow: 0 4px 12px rgba(0,0,0,0.1);
  }

  &::before {
    content: '';
    display: block;
    height: 2px;
    background: linear-gradient(90deg, #0066cc, #00cc66);
  }

  /* Media queries aninhadas */
  @media (max-width: 600px) {
    padding: 0.75rem;

    .card-title {
      font-size: 1rem;
    }
  }
}

Diferenças importantes do Sass: no CSS nativo, & sempre se refere ao seletor pai completo, não há concatenação implícita. Para seletores como .card.dark, você escreve &.dark. Seletores compostos funcionam naturalmente.

7. Combinando as três técnicas em um projeto real

Vamos unir tudo em um exemplo prático: um dashboard com cards de métricas que se adaptam ao container.

@layer reset, base, components, utilities;

@layer reset {
  * { margin: 0; padding: 0; box-sizing: border-box; }
}

@layer base {
  body {
    font-family: system-ui, sans-serif;
    background: #f0f2f5;
    padding: 2rem;
  }
}

@layer components {
  .dashboard {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 1.5rem;
    container-type: inline-size;
    container-name: dashboard;
  }

  .metric-card {
    container-type: inline-size;
    container-name: metric;
    background: white;
    border-radius: 12px;
    padding: 1.5rem;
    box-shadow: 0 2px 8px rgba(0,0,0,0.08);

    .metric-header {
      display: flex;
      align-items: center;
      gap: 0.75rem;
      margin-bottom: 1rem;

      .metric-icon {
        width: 40px;
        height: 40px;
        border-radius: 8px;
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 1.25rem;
      }

      .metric-label {
        font-size: 0.875rem;
        color: #666;
        text-transform: uppercase;
        letter-spacing: 0.05em;
      }
    }

    .metric-value {
      font-size: 2rem;
      font-weight: 700;
      color: #111;
    }

    .metric-change {
      font-size: 0.875rem;
      margin-top: 0.5rem;

      &.positive { color: #00cc66; }
      &.negative { color: #ff4444; }
    }

    @container metric (max-width: 300px) {
      padding: 1rem;

      .metric-header {
        flex-direction: column;
        text-align: center;
      }

      .metric-value {
        font-size: 1.5rem;
        text-align: center;
      }

      .metric-change {
        text-align: center;
      }
    }

    @container metric (min-width: 301px) and (max-width: 450px) {
      .metric-header {
        .metric-icon {
          width: 32px;
          height: 32px;
          font-size: 1rem;
        }
      }

      .metric-value {
        font-size: 1.75rem;
      }
    }
  }
}

@layer utilities {
  .bg-blue { background: #e8f0fe; }
  .bg-green { background: #e6f7ed; }
  .bg-orange { background: #fef3e2; }
}

A estrutura de layers garante que reset, base e componentes tenham prioridades claras. O nesting mantém o código legível e coeso. As container queries fazem cada card se adaptar ao espaço disponível no grid.

8. Considerações finais e próximos passos

O CSS moderno sem frameworks elimina a necessidade de bibliotecas externas para 80% dos casos de uso. O impacto no bundle é zero — tudo é nativo do navegador. A performance melhora porque não há runtime de framework CSS interpretando classes utilitárias.

Para migrar gradualmente de Bootstrap ou Tailwind:
1. Comece substituindo o sistema de grid por CSS Grid nativo com container queries
2. Migre componentes um a um, usando layers para isolar estilos novos dos antigos
3. Adote nesting gradualmente, refatorando arquivos conforme tocar neles

Ferramentas como o DevTools do Chrome já oferecem debugging visual para container queries e layers. A especificação W3C continua evoluindo, com propostas como @scope e @when no horizonte.

O futuro do CSS é nativo, modular e sem dependências. Container queries, layers e nesting são a base dessa nova era.

Referências