Voltar para o Blog

CSS Moderno em 2025: Container Queries, Cascade Layers e Novos Seletores

Olá HaWkers, o CSS passou por uma revolução nos últimos anos. Recursos que antes exigiam JavaScript ou eram simplesmente impossíveis agora são nativos. Se você ainda está preso no CSS de 2020, está perdendo ferramentas incríveis.

Você sabia que agora é possível estilizar um elemento baseado no tamanho do seu container pai, e não apenas da viewport? Vamos explorar os recursos mais poderosos do CSS moderno.

O Estado do CSS em 2025

O CSS evoluiu mais nos últimos 3 anos do que na década anterior. Navegadores modernos agora suportam recursos avançados com excelente compatibilidade.

Suporte dos Navegadores em 2025

Recursos com suporte total (95%+ dos browsers):

  • Container Queries
  • Cascade Layers
  • CSS Nesting
  • Seletor :has()
  • Subgrid
  • Color functions (oklch, color-mix)
  • Scroll-driven animations
  • View transitions

Ainda experimentais:

  • CSS Anchor Positioning
  • Scroll state container queries

Container Queries

Container Queries são provavelmente a adição mais impactante ao CSS desde Flexbox e Grid. Elas permitem estilizar elementos baseado no tamanho do container pai.

Setup Básico

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

/* Ou shorthand */
.card-container {
  container: card / inline-size;
}

/* Aplicar estilos baseado no container */
@container card (min-width: 400px) {
  .card {
    display: grid;
    grid-template-columns: 150px 1fr;
    gap: 1rem;
  }

  .card-image {
    width: 150px;
    aspect-ratio: 1;
  }
}

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

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

Componente Card Responsivo

/* Container queries para cards verdadeiramente responsivos */
.card-wrapper {
  container: card / inline-size;
}

.card {
  display: flex;
  flex-direction: column;
  background: white;
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.card-image {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

.card-content {
  padding: 1rem;
}

.card-title {
  font-size: 1rem;
  font-weight: 600;
  margin-bottom: 0.5rem;
}

.card-description {
  font-size: 0.875rem;
  color: #666;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.card-meta {
  display: none;
}

/* Container médio: layout horizontal */
@container card (min-width: 350px) {
  .card {
    flex-direction: row;
  }

  .card-image {
    width: 120px;
    aspect-ratio: 1;
  }

  .card-content {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: center;
  }
}

/* Container grande: mais detalhes */
@container card (min-width: 500px) {
  .card-image {
    width: 180px;
  }

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

  .card-description {
    -webkit-line-clamp: 3;
  }

  .card-meta {
    display: flex;
    gap: 1rem;
    margin-top: 0.75rem;
    font-size: 0.75rem;
    color: #888;
  }
}

Cascade Layers

Cascade Layers resolvem um dos maiores problemas do CSS: gerenciar especificidade. Elas permitem definir camadas de estilo com precedência controlada.

Definindo Layers

/* Definir ordem das layers (primeira tem menor precedência) */
@layer reset, base, components, utilities;

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

  html {
    font-size: 100%;
    -webkit-text-size-adjust: 100%;
  }
}

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

  a {
    color: #0066cc;
    text-decoration: none;
  }

  a:hover {
    text-decoration: underline;
  }

  h1, h2, h3, h4, h5, h6 {
    line-height: 1.2;
    font-weight: 600;
  }
}

/* Componentes */
@layer components {
  .button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0.75rem 1.5rem;
    font-size: 1rem;
    font-weight: 500;
    border-radius: 8px;
    border: none;
    cursor: pointer;
    transition: all 0.2s ease;
  }

  .button-primary {
    background: #0066cc;
    color: white;
  }

  .button-primary:hover {
    background: #0055aa;
  }

  .card {
    background: white;
    border-radius: 12px;
    padding: 1.5rem;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  }
}

/* Utilitários (maior precedência) */
@layer utilities {
  .text-center { text-align: center; }
  .text-left { text-align: left; }
  .text-right { text-align: right; }

  .flex { display: flex; }
  .grid { display: grid; }
  .hidden { display: none; }

  .mt-1 { margin-top: 0.25rem; }
  .mt-2 { margin-top: 0.5rem; }
  .mt-4 { margin-top: 1rem; }
  .mt-8 { margin-top: 2rem; }

  .gap-1 { gap: 0.25rem; }
  .gap-2 { gap: 0.5rem; }
  .gap-4 { gap: 1rem; }
}

Importando CSS Externo em Layers

/* Importar bibliotecas externas em layers específicas */
@import url('https://unpkg.com/normalize.css') layer(reset);
@import url('./vendor/library.css') layer(vendor);

/* Definir ordem incluindo vendor */
@layer reset, vendor, base, components, utilities;

/* Seus estilos em components vão sobrescrever vendor */
@layer components {
  /* Customizações que sobrescrevem a biblioteca */
  .library-component {
    border-radius: 8px; /* Sobrescreve o original */
  }
}

Seletor :has()

O seletor :has() é frequentemente chamado de "seletor pai" - algo que desenvolvedores pediam há décadas.

Casos de Uso Práticos

/* Estilizar elemento pai baseado no filho */

/* Form com erro */
.form-group:has(input:invalid) {
  border-color: #dc3545;
}

.form-group:has(input:invalid) .form-label {
  color: #dc3545;
}

/* Card com imagem */
.card:has(img) {
  padding: 0;
}

.card:has(img) .card-content {
  padding: 1.5rem;
}

/* Card sem imagem */
.card:not(:has(img)) {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
}

/* Navegação com submenu aberto */
.nav-item:has(.submenu:hover),
.nav-item:has(.submenu:focus-within) {
  background: #f5f5f5;
}

.nav-item:has(.submenu:hover) .submenu,
.nav-item:has(.submenu:focus-within) .submenu {
  display: block;
}

/* Figura com caption */
figure:has(figcaption) img {
  border-radius: 8px 8px 0 0;
}

figure:has(figcaption) figcaption {
  background: #f5f5f5;
  padding: 0.75rem;
  border-radius: 0 0 8px 8px;
}

/* Checkbox customizado que afeta label irmão */
input[type="checkbox"]:checked + label {
  text-decoration: line-through;
  color: #999;
}

/* Grid que se adapta ao conteúdo */
.grid-container:has(> :nth-child(4)) {
  grid-template-columns: repeat(2, 1fr);
}

.grid-container:has(> :nth-child(7)) {
  grid-template-columns: repeat(3, 1fr);
}

Formulários Inteligentes

/* Mostrar/esconder campos baseado em seleções */
.form:has(#option-business:checked) .business-fields {
  display: block;
}

.form:has(#option-personal:checked) .business-fields {
  display: none;
}

/* Botão submit habilitado apenas quando form válido */
.form:has(:invalid) .submit-button {
  opacity: 0.5;
  pointer-events: none;
}

/* Indicador de progresso do form */
.form-progress::before {
  content: '';
  width: 0%;
  height: 4px;
  background: #0066cc;
  transition: width 0.3s ease;
}

.form:has(input:nth-of-type(1):valid) .form-progress::before {
  width: 25%;
}

.form:has(input:nth-of-type(2):valid) .form-progress::before {
  width: 50%;
}

.form:has(input:nth-of-type(3):valid) .form-progress::before {
  width: 75%;
}

.form:has(input:nth-of-type(4):valid) .form-progress::before {
  width: 100%;
  background: #28a745;
}

CSS Nesting Nativo

Finalmente temos nesting nativo no CSS, similar ao Sass/Less.

Sintaxe de Nesting

/* Nesting nativo - sem preprocessador */
.card {
  background: white;
  border-radius: 12px;
  padding: 1.5rem;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);

  /* Elementos filhos */
  & .card-header {
    display: flex;
    align-items: center;
    gap: 1rem;
    margin-bottom: 1rem;
  }

  & .card-title {
    font-size: 1.25rem;
    font-weight: 600;
    color: #333;
  }

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

  & .card-footer {
    margin-top: 1rem;
    padding-top: 1rem;
    border-top: 1px solid #eee;
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
  }

  /* Pseudo-classes */
  &:hover {
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
    transform: translateY(-2px);
  }

  &:focus-within {
    outline: 2px solid #0066cc;
    outline-offset: 2px;
  }

  /* Variantes */
  &.card-featured {
    border: 2px solid #0066cc;

    & .card-title {
      color: #0066cc;
    }
  }

  &.card-danger {
    border-left: 4px solid #dc3545;

    & .card-title::before {
      content: '⚠️ ';
    }
  }

  /* Media queries aninhadas */
  @media (min-width: 768px) {
    padding: 2rem;

    & .card-title {
      font-size: 1.5rem;
    }
  }

  /* Container queries aninhadas */
  @container card (min-width: 400px) {
    flex-direction: row;

    & .card-image {
      width: 150px;
    }
  }
}

Subgrid

Subgrid permite que elementos filhos participem do grid do elemento pai.

Alinhamento Perfeito de Cards

/* Grid principal */
.cards-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 2rem;
}

/* Card usando subgrid para alinhar conteúdo */
.card {
  display: grid;
  grid-template-rows: auto 1fr auto;
  /* Ou com subgrid para herdar rows do pai */
  /* grid-template-rows: subgrid; */
  gap: 1rem;
  background: white;
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.card-image {
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

.card-content {
  padding: 0 1.5rem;
  /* Flexível para ocupar espaço disponível */
}

.card-footer {
  padding: 1rem 1.5rem;
  background: #f8f9fa;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

/* Exemplo com subgrid real */
.features-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: auto auto 1fr auto;
  gap: 2rem;
}

.feature-card {
  display: grid;
  grid-row: span 4;
  grid-template-rows: subgrid;
  background: white;
  border-radius: 12px;
  padding: 1.5rem;
  gap: 0;
}

.feature-icon {
  /* Linha 1: ícone */
  font-size: 2.5rem;
  margin-bottom: 1rem;
}

.feature-title {
  /* Linha 2: título */
  font-size: 1.25rem;
  font-weight: 600;
}

.feature-description {
  /* Linha 3: descrição (flexível) */
  color: #666;
  line-height: 1.6;
}

.feature-link {
  /* Linha 4: link (alinhado em todos os cards) */
  margin-top: 1rem;
  color: #0066cc;
  font-weight: 500;
}

Novas Funções de Cor

CSS moderno oferece funções de cor muito mais poderosas.

OKLCH e Color-Mix

:root {
  /* OKLCH: melhor para criar paletas acessíveis */
  --primary: oklch(55% 0.25 250);
  --primary-light: oklch(70% 0.2 250);
  --primary-dark: oklch(40% 0.25 250);

  /* Gerando variantes automaticamente */
  --primary-hover: oklch(from var(--primary) calc(l - 10%) c h);
  --primary-active: oklch(from var(--primary) calc(l - 20%) c h);

  /* Color-mix para transparências */
  --primary-10: color-mix(in srgb, var(--primary) 10%, transparent);
  --primary-50: color-mix(in srgb, var(--primary) 50%, transparent);

  /* Cores semânticas derivadas */
  --success: oklch(65% 0.2 145);
  --warning: oklch(75% 0.15 85);
  --danger: oklch(55% 0.25 25);
}

.button-primary {
  background: var(--primary);
  color: white;

  &:hover {
    background: var(--primary-hover);
  }

  &:active {
    background: var(--primary-active);
  }
}

/* Tema escuro automático com OKLCH */
@media (prefers-color-scheme: dark) {
  :root {
    --primary: oklch(70% 0.2 250);
    --primary-light: oklch(80% 0.15 250);
    --primary-dark: oklch(60% 0.2 250);
  }
}

Scroll-Driven Animations

Animações baseadas em scroll, sem JavaScript.

/* Definir timeline de scroll */
@keyframes fade-in {
  from {
    opacity: 0;
    transform: translateY(50px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* Aplicar animação baseada em scroll */
.animate-on-scroll {
  animation: fade-in linear;
  animation-timeline: view();
  animation-range: entry 0% cover 40%;
}

/* Parallax com scroll */
@keyframes parallax {
  from {
    transform: translateY(0);
  }
  to {
    transform: translateY(-100px);
  }
}

.parallax-background {
  animation: parallax linear;
  animation-timeline: scroll();
}

/* Progress bar baseada em scroll */
.reading-progress {
  position: fixed;
  top: 0;
  left: 0;
  height: 4px;
  background: #0066cc;
  transform-origin: left;
  animation: grow-progress linear;
  animation-timeline: scroll();
}

@keyframes grow-progress {
  from {
    transform: scaleX(0);
  }
  to {
    transform: scaleX(1);
  }
}

Conclusão

O CSS de 2025 é uma linguagem completamente diferente do CSS de alguns anos atrás. Container Queries, Cascade Layers, :has() e os outros recursos apresentados resolvem problemas que antes exigiam JavaScript ou eram impossíveis.

Para começar hoje:

  1. Experimente Container Queries em um componente
  2. Organize seus estilos com Cascade Layers
  3. Substitua JavaScript por :has() onde possível
  4. Adote CSS Nesting para código mais limpo

A web está evoluindo rapidamente, e o CSS está acompanhando. Desenvolvedores que dominam essas ferramentas criam interfaces mais elegantes com menos código.

Se você quer aprofundar seus conhecimentos em front-end, recomendo que dê uma olhada em outro artigo: React, Vue e Angular em 2025 onde você vai descobrir como esses frameworks se integram com CSS moderno.

Bora pra cima! 🦅

📚 Quer Aprofundar Seus Conhecimentos em JavaScript?

Este artigo cobriu CSS moderno, mas JavaScript continua sendo essencial para qualquer desenvolvedor front-end.

Desenvolvedores que investem em conhecimento sólido e estruturado tendem a ter mais oportunidades no mercado.

Material de Estudo Completo

Se você quer dominar JavaScript do básico ao avançado, preparei um guia completo:

Opções de investimento:

  • 1x de R$9,90 no cartão
  • ou R$9,90 à vista

👉 Conhecer o Guia JavaScript

💡 Material atualizado com as melhores práticas do mercado

Comentários (0)

Esse artigo ainda não possui comentários 😢. Seja o primeiro! 🚀🦅

Adicionar comentário