Op onderliggende status gebaseerde stijl

Webfuncties: :has() :not()

In het verleden konden CSS-selectors alleen naar beneden gaan: je kon een onderliggend element opmaken op basis van het bovenliggende element, maar niet een bovenliggend element op basis van het onderliggende element. De pseudoklasse :has() verandert dit, zodat je een containerelement voorwaardelijk kunt opmaken, afhankelijk van de aanwezigheid of status van de onderliggende elementen.

Door :has() te combineren met pseudo-klassen voor status (zoals :checked, :focus, :valid, :invalid of :not()), kun je complexe, interactieve UI-componenten volledig in CSS bouwen, zonder dat je JavaScript nodig hebt om modifier-klassen (zoals .is-active of .has-error) aan te zetten voor bovenliggende elementen.

Dit is vooral handig voor componenten die moeten reageren op interne interacties, zoals een gelokaliseerde themaschakelaar die reageert op een selectievakje (:checked), een formuliergroep die een fout markeert (:invalid) of een kaart die omhoog komt als een onderliggende link de focus krijgt (:focus-within).

Op status gebaseerde containerstijlen implementeren

VERPLICHT: Je moet de kiezer :has() gebruiken voor het containerelement om de specifieke status (bijv. :checked, :focus, :invalid) van het interactieve onderliggende element te detecteren.

Zo maak je een component waarvan de stijl verandert op basis van de status van een onderliggend element:

  1. Definieer de standaardstijl: Stel css-variabelen in voor de container om de basisstatus te definiëren.
  2. Pas op status gebaseerde overschrijvingen toe: Target de container met :has([child-selector]:[state]) en herdefinieer de CSS-variabelen voor de actieve of alternatieve status.

Voorbeeld: Een component die van thema verandert op basis van een onderliggende schakelaar.

/* 1. Define the default state on the component container */
.theme-card {
  /* Using custom properties makes state-switching cleaner */
  --card-bg: #ffffff;
  --card-text: #333333;
  --card-border: #cccccc;

  background-color: var(--card-bg);
  color: var(--card-text);
  border: 1px solid var(--card-border);

  /* Use a transition for smooth state changes */
  transition: background-color 0.3s, color 0.3s;
}

/* 2. Apply styles when the child enters the specific state */
/* MANDATORY: Target the container and use :has() to check the descendant's state */
.theme-card:has(.theme-toggle:checked) {
  /* Override the properties for the active or alternate state */
  --card-bg: #222222;
  --card-text: #f0f0f0;
  --card-border: #555555;
}
/* You can also combine :has() and :not() to target specific negative states */
/* This selects a card that DOES NOT have a toggle in the checked state */
.theme-card:not(:has(.theme-toggle:checked)) {
  /* Optional: Explicit styles for the unchecked state if needed */
}
<!-- The container element that receives the styling -->
<div class="theme-card">
  <!-- The child element whose state controls the parent -->
  <label>
    <input type="checkbox" class="theme-toggle">
    Enable Dark Mode
  </label>

  <h2>Card Title</h2>
  <p>The style of this entire card is controlled by the checkbox above.</p>
</div>

Prestatietip: Als je :has() gebruikt, beperk je de kiezer tot de meest specifieke container (zoals .theme-card). Veranker deze niet aan elementen op een zeer hoog niveau, zoals body:has(...), als de stijlwijzigingen gelokaliseerd zijn, omdat brede :has()-query's meer herberekeningen van de vormgeving kunnen activeren.

Reservestrategieën

Als de op status gebaseerde stijl essentieel is voor de gebruikerservaring of pagina-indeling, 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 reserveoptie voor essentiële functies implementeert, moet je @supports not selector(:has(*)) in je CSS gebruiken om een traditionele reserveoptie op basis van klassen te definiëren. Als de kritieke statuswijziging afhankelijk is van gebruikersinteractie, moet je ook een klein inline script met CSS.supports() gebruiken om die klasse aan of uit te zetten op basis van de equivalente JavaScript-gebeurtenis (bijvoorbeeld change, focus, blur) die de statuswijziging vertegenwoordigt.

/* 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 overrides */
  .theme-card.is-active {
    --card-bg: #222222;
    --card-text: #f0f0f0;
    --card-border: #555555;
  }
}
/* Fallback JavaScript for older browsers */
/* Check for support before running the script to avoid unnecessary work in modern browsers */
if (!CSS.supports('selector(:has(*))')) {
  const toggle = document.querySelector('.theme-toggle');
  const card = document.querySelector('.theme-card');

  if (toggle && card) {
    // Manually toggle the fallback class when the input state changes
    toggle.addEventListener('change', (e) => {
      card.classList.toggle('is-active', e.target.checked);
    });
  }
}