Estilo com base no conteúdo

Recursos da Web: :has() :not()

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:

  1. Defina o estilo padrão: aplique os estilos de layout base ao elemento do contêiner (por exemplo, uma pilha simples de coluna única).
  2. 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');
    }
  });
}