No passado, para aplicar layouts diferentes a um componente com base no conteúdo, era necessário usar JavaScript ou lógica condicional na linguagem de modelo HTML para injetar classes modificadoras (como .card--has-image ou .card--text-only).
A pseudoclasse :has() elimina essa necessidade ao atuar como um seletor pai. Ele permite estilizar condicionalmente um elemento de contêiner com base na presença ou ausência de elementos descendentes específicos.
Com :has(), é fácil definir variações de layout distintas inteiramente em CSS com base no conteúdo real do DOM de um componente. Também é possível combinar com :not() para segmentar explicitamente a ausência de conteúdo para definir layouts padrão.
Implementar o estilo de contêiner com base no conteúdo
OBRIGATÓRIO: use o seletor :has() no elemento do contêiner para detectar a presença de conteúdo filho específico.
Para criar um componente que muda o layout com base no conteúdo:
- Defina o estilo padrão: aplique os estilos de layout base ao elemento do contêiner (por exemplo, uma pilha simples de coluna única).
- Aplicar substituições com base no conteúdo: segmente o contêiner com
:has([child-selector])e aplique os novos estilos de layout para quando esse conteúdo estiver presente (por exemplo, um grid de várias colunas).
Exemplo: um componente de card que muda para um layout lado a lado se uma imagem estiver presente.
/* 1. Define the default state on the component container */
/* This applies when there is NO image */
.article-card {
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1.5rem;
border: 1px solid #ccc;
border-radius: 8px;
}
/* 2. Apply styles when a specific child element is present */
/* MANDATORY: Target the container and use :has() to check for the descendant */
.article-card:has(img) {
/* Change the layout to a row if an image exists */
flex-direction: row;
align-items: center;
}
/* Optional: Style the image itself (no :has() needed here) */
.article-card img {
width: 150px;
height: auto;
border-radius: 4px;
}
/* You can also combine :has() and :not() to explicitly target the ABSENCE of content */
/* This selects a card that DOES NOT have an image */
.article-card:not(:has(img)) {
/* E.g., apply a different background color for text-only cards */
background-color: #f9f9f9;
}
<!-- Assume an <h1> precedes these components in the document layout -->
<!-- A card WITH an image (will use row layout) -->
<article class="article-card">
<img src="thumbnail.jpg" alt="Article thumbnail" />
<div class="content">
<h2>Card With Image</h2>
<p>This card lays out its content horizontally.</p>
</div>
</article>
<!-- A card WITHOUT an image (will use default column layout) -->
<article class="article-card">
<div class="content">
<h2>Text-Only Card</h2>
<p>This card lays out its content vertically, and gets its background color from the :not(:has()) rule.</p>
</div>
</article>
Dica de desempenho: ao usar :has(), limite o seletor ao contêiner de componente mais específico possível (como .article-card). Evite ancorá-lo a elementos de nível muito alto, como body:has(img), se as mudanças de estilo forem muito localizadas, já que consultas :has() amplas podem acionar mais recálculos de layout.
Estratégias de fallback
Se o estilo de layout baseado em conteúdo for essencial para a experiência do usuário ou o design da página, forneça um substituto para navegadores que não oferecem suporte ao seletor :has(). Para efeitos puramente decorativos, :has() pode ser usado como um aprimoramento progressivo sem substituto.
OBRIGATÓRIO: ao implementar um substituto para layouts críticos, use @supports not selector(:has(*)) no CSS para definir um substituto tradicional baseado em classe (por exemplo, .has-image).
Ao contrário do estilo interativo baseado em estado, a presença de conteúdo geralmente é conhecida no tempo de renderização. O substituto mais robusto é fazer com que o mecanismo de modelo do lado do servidor (ou gerador de site estático) injete uma classe como .has-image no contêiner se o elemento filho (como uma imagem) existir nos dados.
Se a renderização do lado do servidor não for uma opção, use um pequeno script com CSS.supports() para detectar o conteúdo e anexar a classe no carregamento ou após a injeção de conteúdo dinâmico.
/* Fallback CSS for older browsers */
/* We check if the browser DOES NOT support the :has() selector */
@supports not selector(:has(*)) {
/* Define a traditional modifier class that applies the exact same layout overrides */
.article-card.has-image {
flex-direction: row;
align-items: center;
}
.article-card:not(.has-image) {
background-color: #f9f9f9;
}
}
/* Fallback JavaScript for older browsers (if not using SSR to add the class) */
/* Check for support before running the script to avoid unnecessary work in modern browsers */
if (!CSS.supports('selector(:has(*))')) {
// Find all components that need checking
const cards = document.querySelectorAll('.article-card');
cards.forEach(card => {
// If the critical content exists, manually add the fallback class
if (card.querySelector('img')) {
card.classList.add('has-image');
}
});
}