Op content gebaseerde stijl

Webfuncties: :has() :not()

In het verleden had je JavaScript of voorwaardelijke logica in je HTML-templatetaal nodig om modifier-klassen (zoals .card--has-image of .card--text-only) te injecteren als je verschillende lay-outs wilde toepassen op een component op basis van de content.

De pseudoklasse :has() maakt dit overbodig door als bovenliggende kiezer te fungeren. Hiermee kun je een containerelement voorwaardelijk opmaken op basis van de aan- of afwezigheid van specifieke onderliggende elementen.

Met :has() kun je makkelijk verschillende indelingsvarianten definiëren in CSS op basis van de werkelijke DOM-content van een component. Je kunt deze ook combineren met :not() om expliciet de afwezigheid van content te targeten om standaardindelingen te definiëren.

Op content gebaseerde containerstijlen implementeren

VERPLICHT: Je moet de kiezer :has() gebruiken voor het containerelement om de aanwezigheid van specifieke onderliggende content te detecteren.

Zo maak je een component waarvan de indeling verandert op basis van de content:

  1. Definieer de standaardstijl: Pas de basisindelingsstijlen toe op het containerelement (bijvoorbeeld een eenvoudige stapel met één kolom).
  2. Contentgebaseerde overschrijvingen toepassen: Target de container met :has([child-selector]) en pas de nieuwe vormgevingsstijlen toe voor wanneer die content aanwezig is (bijvoorbeeld een raster met meerdere kolommen).

Voorbeeld: Een kaartcomponent die overschakelt naar een indeling naast elkaar als er een afbeelding aanwezig is.

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

Prestatietip: Als u :has() gebruikt, beperkt u de kiezer tot de meest specifieke componentcontainer (zoals .article-card). Veranker de kiezer niet aan elementen op een zeer hoog niveau, zoals body:has(img), als de stijlveranderingen zeer lokaal zijn, omdat brede :has()-query's meer herberekeningen van de indeling kunnen veroorzaken.

Reservestrategieën

Als de op content gebaseerde vormgeving van de lay-out essentieel is voor de gebruikerservaring of het paginaontwerp, moet je een fallback bieden voor browsers die de kiezer :has() niet ondersteunen. Voor puur decoratieve effecten kan :has() worden gebruikt als progressive enhancement zonder fallback.

VERPLICHT: Als je een fallback implementeert voor kritieke indelingen, moet je @supports not selector(:has(*)) gebruiken in je CSS om een traditionele op klassen gebaseerde fallback te definiëren (bijv. .has-image).

In tegenstelling tot interactieve op status gebaseerde opmaak is de aanwezigheid van content meestal bekend op het moment van weergave. De meest robuuste fallback is om je templating-engine aan de serverzijde (of generator voor statische sites) een klasse zoals .has-image in de container te laten injecteren als het onderliggende element (zoals een afbeelding) in de gegevens bestaat.

Als weergave aan de serverzijde geen optie is, moet u een klein script met CSS.supports() gebruiken om de content te detecteren en de klasse toe te voegen bij het laden of na de injectie van dynamische content.

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