Back

Guia Completo do Seletor CSS :has(): O Seletor Pai que Muda Tudo

Por mais de duas décadas, desenvolvedores web pediram uma única coisa: um seletor de pai no CSS. A capacidade de estilizar um elemento baseado em seus filhos parecia um sonho impossível. JavaScript era nossa única saída, levando a inúmeras gambiarras, dores de cabeça com gerenciamento de estado e compromissos de performance.

Então veio o :has().

O seletor CSS :has() muda fundamentalmente como pensamos sobre estilização de interfaces web. Não é apenas um seletor de pai—é uma pseudo-classe relacional que permite selecionar elementos baseado no que eles contêm, o que vem depois deles, ou virtualmente qualquer relação que você possa expressar em CSS.

Neste guia completo, vamos explorar tudo o que você precisa saber sobre :has(): desde a sintaxe básica até padrões avançados, considerações de performance e aplicações do mundo real que transformarão sua arquitetura CSS.

A Longa Espera por um Seletor de Pai

Antes de mergulhar no :has(), vamos entender por que essa funcionalidade demorou tanto para chegar e por que ela é tão importante.

O Problema Histórico

Seletores CSS tradicionais funcionam em uma direção: de pai para filho. Você pode estilizar um filho baseado em seu pai, mas nunca o contrário.

/* Isso funciona - estilizando filhos baseado no pai */ .card .title { font-size: 1.5rem; } /* Isso era impossível - estilizando pai baseado no filho */ /* .card:contains(.featured-badge) { ... } ← CSS inválido (antes do :has()) */

Essa limitação forçou desenvolvedores a adotar padrões deselegantes:

  1. Adicionar classes modificadoras: .card.has-featured-badge { ... }
  2. Estilização baseada em JavaScript: Ouvir mudanças do DOM e alternar classes
  3. Reestruturar HTML: Mover elementos para se adequar às limitações do CSS

Cada abordagem tinha desvantagens. Classes modificadoras criavam acoplamento entre lógica e estilos. Soluções JavaScript adicionavam complexidade e possíveis problemas de performance. Reestruturação HTML comprometia a marcação semântica.

Por Que Demorou Tanto?

Os fornecedores de navegadores hesitaram em implementar seletores de pai devido a preocupações com performance. Seletores CSS são avaliados da direita para a esquerda para eficiência. Um seletor de pai teoricamente poderia exigir que o navegador:

  1. Encontre todos os elementos descendentes correspondentes
  2. Suba a árvore DOM para cada correspondência
  3. Aplique estilos aos ancestrais

Essa abordagem poderia causar gargalos significativos de renderização em páginas complexas. A especificação do :has() aborda essas preocupações com regras específicas de análise e avaliação, tornando-o eficiente o suficiente para uso no mundo real.

Entendendo os Fundamentos do :has()

A pseudo-classe :has() aceita uma lista de seletores relativos como argumento e corresponde a elementos que têm pelo menos um descendente correspondendo a esse seletor.

Sintaxe Básica

pai:has(seletor-filho) { /* estilos aplicados ao pai */ }

O elemento antes do :has() é estilizado quando contém qualquer elemento correspondendo ao seletor dentro dos parênteses.

Seu Primeiro Exemplo com :has()

Vamos começar com um exemplo prático. Considere um componente de card:

<div class="card"> <img src="product.jpg" alt="Product"> <h3>Título do Produto</h3> <p>Descrição do produto...</p> </div> <div class="card"> <h3>Card Somente Texto</h3> <p>Este card não tem imagem.</p> </div>

Anteriormente, estilizar cards de forma diferente baseado em se eles contêm uma imagem exigia JavaScript ou classes CSS separadas. Com :has():

/* Estilos padrão do card */ .card { padding: 1rem; border-radius: 8px; background: white; } /* Cards COM imagens recebem um layout diferente */ .card:has(img) { display: grid; grid-template-columns: 200px 1fr; gap: 1rem; } /* Cards SEM imagens centralizam seu conteúdo */ .card:not(:has(img)) { text-align: center; max-width: 400px; }

Isso é CSS puro. Sem JavaScript. Sem classes extras. A estilização se adapta automaticamente baseado no conteúdo.

Além da Seleção de Pai: :has() como Seletor Relacional

Embora "seletor de pai" capture o caso de uso mais comum, :has() é muito mais poderoso. É uma pseudo-classe relacional que pode expressar relações complexas entre elementos.

Seleção de Irmãos com :has()

Você pode selecionar elementos baseado em seus irmãos usando :has() com combinadores de irmãos:

/* Selecionar um label que tem um input obrigatório depois dele */ label:has(+ input:required) { font-weight: bold; } label:has(+ input:required)::after { content: " *"; color: #e74c3c; }

Esse padrão é incrivelmente útil para estilização de formulários sem JavaScript.

Seleção de Ancestrais

:has() pode procurar múltiplos níveis de profundidade:

/* Estilizar uma seção se ela contém QUALQUER erro em qualquer lugar */ .form-section:has(.error-message) { border-left: 4px solid #e74c3c; background: #fdf2f2; } /* Estilizar uma linha de tabela se ela contém uma célula editável */ tr:has(td[contenteditable="true"]) { background: #fffef0; }

Combinando Múltiplas Condições

Você pode encadear múltiplas condições :has():

/* Card com imagem E badge de destaque */ .card:has(img):has(.featured-badge) { box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15); border: 2px solid gold; }

Ou use uma lista de seletores dentro do :has():

/* Card com vídeo OU imagem */ .card:has(img, video) { min-height: 300px; }

Casos de Uso Reais e Padrões

Vamos explorar aplicações práticas que mostram o verdadeiro poder do :has().

1. Estilização de Validação de Formulários

Um dos usos mais impactantes do :has() é feedback de validação de formulários apenas com CSS:

/* Estilizar grupo de formulário baseado na validade do input */ .form-group:has(input:invalid:not(:placeholder-shown)) { --input-border-color: #e74c3c; --input-bg: #fdf2f2; } .form-group:has(input:valid:not(:placeholder-shown)) { --input-border-color: #27ae60; --input-bg: #f0fdf4; } .form-group input { border: 2px solid var(--input-border-color, #ddd); background: var(--input-bg, white); transition: all 0.2s ease; } /* Mostrar mensagem de validação somente quando inválido */ .form-group .error-message { display: none; color: #e74c3c; font-size: 0.875rem; margin-top: 0.5rem; } .form-group:has(input:invalid:not(:placeholder-shown)) .error-message { display: block; }

Isso cria uma UI de validação completa e reativa sem uma única linha de JavaScript.

2. Estados Ativos de Navegação

Estilizar itens de navegação baseado na página atual ou estado ativo:

/* Destacar pai do dropdown quando qualquer link filho está ativo */ .nav-dropdown:has(.nav-link.active) > .dropdown-toggle { color: var(--primary-color); font-weight: bold; } /* Mostrar indicador de dropdown quando tem itens de submenu */ .nav-item:has(.submenu)::after { content: "▼"; font-size: 0.75em; margin-left: 0.5rem; }

3. Tratamento de Estados Vazios

Detectar e estilizar containers vazios:

/* Estilizar container quando não tem itens */ .item-grid:not(:has(.item)) { display: flex; align-items: center; justify-content: center; min-height: 300px; } .item-grid:not(:has(.item))::before { content: "Nenhum item para exibir"; color: #999; font-style: italic; }

4. Consultas de Quantidade

Aqui é onde :has() fica criativo. Você pode estilizar elementos baseado na contagem de irmãos:

/* Estilizar diferentemente quando há apenas um item */ .item:only-child { width: 100%; } /* Quando há exatamente dois itens */ .item:first-child:nth-last-child(2), .item:last-child:nth-last-child(2) { width: 50%; } /* Estilizar o container quando tem mais de 3 itens */ .item-container:has(.item:nth-child(4)) { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }

5. Gerenciamento de Foco

Implementar estados de foco avançados para acessibilidade:

/* Destacar o card inteiro quando qualquer elemento focável está focado */ .card:has(:focus-visible) { outline: 3px solid var(--focus-color); outline-offset: 2px; } /* Escurecer outros cards quando um está focado (para navegação por teclado) */ .card-container:has(.card:focus-visible) .card:not(:focus-visible):not(:has(:focus-visible)) { opacity: 0.7; }

6. Comportamento Responsivo de Componentes

Criar componentes que se adaptam baseado em seu conteúdo:

/* Alternar layout do header baseado no conteúdo */ .header:has(.search-bar):has(.user-menu) { display: grid; grid-template-columns: auto 1fr auto; } .header:has(.search-bar):not(:has(.user-menu)) { display: grid; grid-template-columns: auto 1fr; } /* Ajustar largura da sidebar quando seções expandidas existem */ .sidebar:has(.section.expanded) { width: 320px; } .sidebar:not(:has(.section.expanded)) { width: 240px; }

7. Melhorias de Tabela

Estilizar tabelas dinamicamente baseado no conteúdo:

/* Destacar linha quando checkbox está marcado */ tr:has(input[type="checkbox"]:checked) { background: #e3f2fd; } /* Estilizar header diferentemente quando tabela tem colunas ordenáveis */ table:has(th[data-sortable]) thead { cursor: pointer; } table:has(th[data-sortable]) th:hover { background: #f5f5f5; } /* Adicionar indicador visual quando tabela está vazia */ table:not(:has(tbody tr)) { position: relative; } table:not(:has(tbody tr))::after { content: "Nenhum dado disponível"; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #999; }

Padrões e Técnicas Avançadas

Combinando :has() com :not()

A combinação de :has() e :not() desbloqueia poderosos padrões de negação:

/* Estilizar elementos que NÃO contêm algo */ .container:not(:has(.advertisement)) { /* Estilos de experiência premium sem anúncios */ padding: 2rem; } /* Cards sem mídia */ .card:not(:has(img, video, iframe)) { /* Estilos de card somente texto */ font-size: 1.1rem; line-height: 1.8; }

Debug com :has()

Use :has() para depuração em tempo de desenvolvimento:

/* Destacar imagens sem texto alt durante desenvolvimento */ img:not([alt]), img[alt=""] { outline: 5px solid red !important; } /* Destacar formulários com actions vazias */ form:not([action]), form[action=""] { outline: 3px dashed orange !important; } /* Encontrar links que abrem em novas abas sem rel="noopener" */ a[target="_blank"]:not([rel*="noopener"]) { outline: 3px solid purple !important; }

O Padrão do Combinador para Frente

Use :has() com o combinador de irmão seguinte para seleção de elementos futuros:

/* Estilizar um elemento baseado no que vem DEPOIS dele */ h2:has(+ .special-content) { /* Este h2 é seguido por conteúdo especial */ color: var(--accent-color); border-bottom: 3px solid currentColor; } /* Criar efeito de seletor de "irmão anterior" */ .item:has(+ .item.active) { /* Estilizar o item ANTES do ativo */ border-right: none; }

Considerações de Performance

Embora navegadores modernos lidem com :has() eficientemente, entender as implicações de performance ajuda você a escrever CSS otimizado.

Como os Navegadores Avaliam :has()

Navegadores implementam :has() com otimizações específicas:

  1. Limites de invalidação: Navegadores definem limites de quão profundo :has() vai procurar
  2. Cache baseado no sujeito: Uma vez correspondido, resultados são cacheados para o elemento sujeito
  3. Processamento em lote de mutações: Mudanças no DOM são agrupadas antes da reavaliação

Melhores Práticas para Performance

1. Seja Específico com Seletores

/* ❌ Seletor amplo - avalia para todas as divs */ div:has(img) { ... } /* ✅ Seletor específico - limita escopo */ .card:has(img) { ... }

2. Evite Aninhamento Profundo

/* ❌ Aninhamento profundo requer atravessar toda a subárvore */ .page:has(.section .container .row .col .card .badge) { ... } /* ✅ Relações diretas ou rasas */ .card:has(> .badge) { ... }

3. Use o Combinador de Filho Direto Quando Possível

/* ❌ Procura todos os descendentes */ .menu:has(.active) { ... } /* ✅ Só verifica filhos diretos */ .menu:has(> .menu-item.active) { ... }

4. Limite o Comprimento da Lista de Seletores

/* ❌ Listas de seletores longas no :has() */ .container:has(img, video, audio, iframe, canvas, svg, object, embed) { ... } /* ✅ Agrupamento semântico com atributos de dados */ .container:has([data-media]) { ... }

Medindo o Impacto na Performance

Para medir a performance do :has():

// Usar a API de Performance const observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (entry.name === 'style-recalc') { console.log(`Recálculo de estilo: ${entry.duration}ms`); } } }); observer.observe({ entryTypes: ['measure'] });

Ou use as DevTools do navegador:

  1. Abra a aba Performance
  2. Grave enquanto interage com elementos que disparam reavaliação do :has()
  3. Procure por entradas "Recalculate Style"

Suporte de Navegadores e Fallbacks

No final de 2024, :has() desfruta de excelente suporte de navegadores:

  • Chrome: 105+ (Agosto 2022)
  • Edge: 105+ (Agosto 2022)
  • Safari: 15.4+ (Março 2022)
  • Firefox: 121+ (Dezembro 2023)
  • Opera: 91+

Isso significa que mais de 95% dos usuários globais podem usar :has() hoje.

Criando Fallbacks

Para suporte de navegadores legados, use feature queries:

/* Estilos de fallback padrão */ .card { display: block; } /* Melhoria progressiva com :has() */ @supports selector(:has(*)) { .card:has(img) { display: grid; grid-template-columns: 200px 1fr; } }

O Padrão @supports

Um padrão robusto para fallbacks do :has():

/* Estilos base - funcionam em todo lugar */ .form-group { margin-bottom: 1rem; } .form-group.has-error { border-color: red; } /* Navegadores modernos com suporte a :has() */ @supports selector(:has(*)) { /* Remover uso de classe dependente de JavaScript */ .form-group.has-error { border-color: initial; } /* Usar :has() em vez disso */ .form-group:has(input:invalid:not(:placeholder-shown)) { border-color: red; } }

Erros Comuns e Como Evitá-los

Erro 1: Entender Mal a Especificidade do :has()

Um equívoco comum é que :has() não adiciona especificidade. Na realidade, :has() assume a especificidade do seletor mais específico em seu argumento.

/* Cálculo de especificidade */ .card { ... } /* (0, 1, 0) */ .card:has(img) { ... } /* (0, 1, 1) -> porque img é (0, 0, 1) */ .card:has(#hero) { ... } /* (1, 1, 0) -> porque #hero é (1, 0, 0) */

Isso é diferente de :where(), que sempre tem especificidade 0. Lembre-se que :has() aumenta o peso do seletor.

/* A especificidade aumenta naturalmente */ .card:has(img) { ... }

Erro 2: Dependência Excessiva do :has()

Nem tudo precisa de :has(). Soluções mais simples frequentemente existem:

/* ❌ Complicando demais */ .btn:has(svg):has(span) { ... } /* ✅ Mais simples com uma classe utilitária */ .btn.icon-button { ... }

Erro 3: Ignorar Performance do Seletor

/* ❌ Caro - verifica cada elemento */ *:has(.some-class) { ... } /* ✅ Com escopo apropriado */ .component:has(.some-class) { ... }

Erro 4: Dependências Circulares

Alguns seletores podem criar loops infinitos:

/* ⚠️ Tenha cuidado com padrões auto-referenciais */ .item:has(+ .item:has(+ .item)) { ... }

O Futuro da Seleção CSS

O seletor :has() representa uma mudança fundamental nas capacidades do CSS, mas é apenas o começo. Especificações CSS futuras estão explorando:

  1. CSS Nesting: Agora disponível, funciona lindamente com :has()
  2. Animações orientadas por scroll: Combinar com :has() para efeitos de scroll complexos
  3. Container queries: Parear com :has() para componentes verdadeiramente responsivos
  4. CSS Mixins: Padrões de estilo reutilizáveis que poderiam aproveitar :has()

:has() com CSS Nesting

O aninhamento CSS moderno torna :has() ainda mais poderoso:

.card { padding: 1rem; &:has(img) { display: grid; grid-template-columns: 200px 1fr; & img { border-radius: 8px; } } &:has(.badge) { position: relative; & .badge { position: absolute; top: -10px; right: -10px; } } }

:has() com Container Queries

O máximo em estilização baseada em componentes:

.card-container { container-type: inline-size; container-name: card; } @container card (min-width: 400px) { .card:has(img) { grid-template-columns: 250px 1fr; } } @container card (max-width: 399px) { .card:has(img) { grid-template-columns: 1fr; } }

Refatoração Prática: Antes e Depois

Vamos ver um exemplo de refatoração do mundo real. Considere um componente de navegação:

Antes do :has() (JavaScript Necessário)

<nav class="main-nav"> <ul class="nav-list"> <li class="nav-item has-submenu expanded"> <a href="#">Produtos</a> <ul class="submenu"> <li><a href="#" class="active">Software</a></li> <li><a href="#">Hardware</a></li> </ul> </li> </ul> </nav>
// JavaScript necessário para gerenciar essas classes document.querySelectorAll('.nav-item').forEach(item => { if (item.querySelector('.submenu')) { item.classList.add('has-submenu'); } if (item.querySelector('.active')) { item.classList.add('expanded'); } });
.nav-item.has-submenu > a::after { content: "▼"; } .nav-item.expanded > .submenu { display: block; }

Depois do :has() (CSS Puro)

<nav class="main-nav"> <ul class="nav-list"> <li class="nav-item"> <a href="#">Produtos</a> <ul class="submenu"> <li><a href="#" class="active">Software</a></li> <li><a href="#">Hardware</a></li> </ul> </li> </ul> </nav>
/* Sem JavaScript necessário! */ .nav-item:has(.submenu) > a::after { content: "▼"; } .nav-item:has(.active) > .submenu { display: block; } .nav-item:has(.active) > a { font-weight: bold; color: var(--primary); }

Os benefícios:

  • HTML mais limpo: Sem classes de estado
  • Sem JavaScript: Remove uma dependência em tempo de execução
  • Atualizações automáticas: Mudanças no DOM auto-atualizam estilos
  • Melhor performance: CSS é mais rápido que JavaScript para isso

Conclusão

O seletor CSS :has() é mais que um seletor de pai—é uma mudança de paradigma em como podemos expressar lógica de estilização. Depois de duas décadas de gambiarras, finalmente temos uma forma nativa de selecionar elementos baseado em suas relações com outros elementos.

Pontos Principais

  1. :has() é uma pseudo-classe relacional, não apenas um seletor de pai. Pode expressar relações de irmãos, condições de descendentes e combinações lógicas complexas.

  2. O suporte de navegadores é excelente com mais de 95%. Você pode usar :has() hoje com fallbacks simples para navegadores legados.

  3. Performance geralmente não é uma preocupação para seletores bem delimitados. Seja específico, evite aninhamento profundo e use combinadores de filho direto quando possível.

  4. :has() reduz dependência de JavaScript. Muitos padrões interativos que exigiam JavaScript agora podem ser CSS puro.

  5. Combine com recursos CSS modernos como nesting e container queries para estilização baseada em componentes ainda mais poderosa.

A plataforma web continua evoluindo, fechando gaps que antes exigiam gambiarras em JavaScript. O seletor :has() é uma das adições mais significativas ao CSS em anos, e dominá-lo fará de você um desenvolvedor frontend mais eficaz.

Comece pequeno—encontre um padrão de estilo dependente de JavaScript em seu código e veja se :has() pode simplificá-lo. Você ficará surpreso com a frequência com que a resposta é sim.

CSSWeb DevelopmentFrontendPerformanceCSS Selectors

Explore ferramentas relacionadas

Experimente estas ferramentas gratuitas do Pockit