Stile basato sui contenuti

Funzionalità web: :has() :not()

In passato, per applicare layout diversi a un componente in base ai suoi contenuti era necessario utilizzare JavaScript o la logica condizionale nel linguaggio di creazione dei modelli HTML per inserire classi di modificatori (come .card--has-image o .card--text-only).

La pseudoclasse :has() elimina questa necessità fungendo da selettore principale. Consente di applicare uno stile condizionale a un elemento contenitore in base alla presenza o meno di elementi discendenti specifici.

Utilizzando :has(), puoi definire facilmente varianti di layout distinte interamente in CSS in base ai contenuti DOM effettivi di un componente. Se vuoi, puoi anche combinarlo con :not() per scegliere esplicitamente come target l'assenza di contenuti per definire i layout predefiniti.

Implementazione dello stile del contenitore basato sui contenuti

OBBLIGATORIO: devi utilizzare il selettore :has() sull'elemento contenitore per rilevare la presenza di contenuti secondari specifici.

Per creare un componente che modifica il proprio layout in base ai contenuti:

  1. Definisci lo stile predefinito: applica gli stili di layout di base all'elemento contenitore (ad es. un semplice stack a colonna singola).
  2. Applica override basati sui contenuti: scegli come target il container con :has([child-selector]) e applica i nuovi stili di layout per quando sono presenti i contenuti (ad es. una griglia a più colonne).

Esempio: un componente scheda che passa a un layout affiancato se è presente un'immagine.

/* 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>

Suggerimento per le prestazioni: quando utilizzi :has(), limita l'ambito del selettore al container del componente più specifico possibile (ad esempio .article-card). Evita di ancorarlo a elementi di livello molto elevato come body:has(img) se le modifiche allo stile sono molto localizzate, poiché le query :has() ampie possono attivare più ricalcoli del layout.

Strategie di riserva

Se lo stile del layout basato sui contenuti è fondamentale per l'esperienza utente o il design della pagina, devi fornire un'alternativa per i browser che non supportano il selettore :has(). Per effetti puramente decorativi, :has() può essere utilizzato come potenziamento progressivo senza un comportamento di riserva.

OBBLIGATORIO: quando implementi un comportamento di riserva per i layout critici, devi utilizzare @supports not selector(:has(*)) nel CSS per definire un comportamento di riserva tradizionale basato su classi (ad es. .has-image).

A differenza dello stile interattivo basato sullo stato, la presenza di contenuti è in genere nota al momento del rendering. Il fallback più efficace consiste nel fare in modo che il motore di template lato server (o il generatore di siti statici) inserisca una classe come .has-image nel contenitore se l'elemento secondario (come un'immagine) esiste nei dati.

Se il rendering lato server non è un'opzione, devi utilizzare un piccolo script con CSS.supports() per rilevare i contenuti e aggiungere la classe al caricamento o dopo l'inserimento di contenuti dinamici.

/* 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');
    }
  });
}