Auparavant, pour appliquer différentes mises en page à un composant en fonction de son contenu, il fallait utiliser JavaScript ou une logique conditionnelle dans votre langage de création de modèles HTML pour injecter des classes de modificateurs (comme .card--has-image ou .card--text-only).
La pseudo-classe :has() élimine ce besoin en agissant comme un sélecteur parent. Elle vous permet d'appliquer un style conditionnel à un élément conteneur en fonction de la présence ou de l'absence d'éléments descendants spécifiques.
Grâce à :has(), vous pouvez facilement définir des variantes de mise en page distinctes entièrement en CSS en fonction du contenu DOM réel d'un composant. Vous pouvez également la combiner avec :not() pour cibler explicitement l'absence de contenu afin de définir des mises en page par défaut.
Implémenter un style de conteneur basé sur le contenu
OBLIGATOIRE : Vous devez utiliser le sélecteur :has() sur l'élément conteneur pour détecter la présence d'un contenu enfant spécifique.
Pour créer un composant qui modifie sa mise en page en fonction de son contenu :
- Définissez le style par défaut : appliquez les styles de mise en page de base à l'élément conteneur (par exemple, une simple pile à une seule colonne).
- Appliquer des remplacements basés sur le contenu : ciblez le conteneur avec
:has([child-selector])et appliquez les nouveaux styles de mise en page lorsque ce contenu est présent (par exemple, une grille multicolonne).
Exemple : Un composant de carte qui passe à une mise en page côte à côte si une image est présente.
/* 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>
Conseil d'optimisation : Lorsque vous utilisez :has(), limitez le sélecteur au conteneur de composants le plus spécifique possible (comme .article-card). Évitez de l'ancrer à des éléments de très haut niveau comme body:has(img) si les modifications de style sont très localisées, car les requêtes :has() larges peuvent déclencher davantage de recalculs de mise en page.
Stratégies de remplacement
Si le style de mise en page basé sur le contenu est essentiel pour l'expérience utilisateur ou la conception de la page, vous devez fournir une solution de remplacement pour les navigateurs qui ne sont pas compatibles avec le sélecteur :has(). Pour les effets purement décoratifs, :has() peut être utilisé comme amélioration progressive sans solution de remplacement.
OBLIGATOIRE : Lorsque vous implémentez une solution de remplacement pour les mises en page critiques, vous devez utiliser @supports not selector(:has(*)) dans votre CSS pour définir une solution de remplacement traditionnelle basée sur les classes (par exemple, .has-image).
Contrairement au style interactif basé sur l'état, la présence de contenu est généralement connue au moment de l'affichage. La solution de remplacement la plus robuste consiste à faire en sorte que votre moteur de création de modèles côté serveur (ou générateur de site statique) injecte une classe telle que .has-image dans le conteneur si l'élément enfant (comme une image) existe dans les données.
Si le rendu côté serveur n'est pas possible, vous devez utiliser un petit script avec CSS.supports() pour détecter le contenu et ajouter la classe au chargement ou après l'injection de contenu dynamique.
/* 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');
}
});
}