Statusbasiertes Styling für untergeordnete Elemente

Webfunktionen: :has() :not()

Bisher konnten CSS-Selektoren nur nach unten traversieren. Sie konnten ein untergeordnetes Element basierend auf seinem übergeordneten Element formatieren, aber nicht umgekehrt. Mit der Pseudoklasse :has() ändert sich das. Sie können ein Containerelement je nach Vorhandensein oder Status seiner untergeordneten Elemente bedingt formatieren.

Durch die Kombination von :has() mit Status-Pseudoklassen (wie :checked, :focus, :valid, :invalid oder :not()) können Sie komplexe, interaktive UI-Komponenten vollständig in CSS erstellen, ohne JavaScript zu benötigen, um „Modifikator“-Klassen (wie .is-active oder .has-error) für übergeordnete Elemente umzuschalten.

Das ist besonders nützlich für Komponenten, die auf interne Interaktionen reagieren müssen, z. B. ein lokalisierter Design-Umschalter, der auf ein Kästchen (:checked) reagiert, eine Formulargruppe, die einen Fehler hervorhebt (:invalid), oder eine Karte, die hervorgehoben wird, wenn ein untergeordneter Link fokussiert wird (:focus-within).

Statusbasiertes Container-Styling implementieren

OBLIGATORISCH: Sie müssen den Selektor :has() für das Containerelement verwenden, um den spezifischen Status (z.B. :checked, :focus, :invalid) des interaktiven untergeordneten Elements zu erkennen.

So erstellen Sie eine Komponente, deren Stil sich je nach Status eines untergeordneten Elements ändert:

  1. Standardstil definieren: Legen Sie CSS-Variablen für den Container fest, um seinen Grundzustand zu definieren.
  2. Zustandsbasierte Überschreibungen anwenden: Richten Sie das Targeting des Containers mit :has([child-selector]:[state]) ein und definieren Sie die CSS-Variablen für den aktiven oder alternativen Zustand neu.

Beispiel: Eine Komponente, die das Design basierend auf einem untergeordneten Schalter ändert.

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

Leistungstipp: Wenn Sie :has() verwenden, sollten Sie den Selektor auf den spezifischsten Container beschränken (z. B. .theme-card). Vermeiden Sie es, ihn an sehr übergeordnete Elemente wie body:has(...) zu binden, wenn die Stiländerungen lokalisiert sind, da breite :has()-Abfragen mehr Layoutneuberechnungen auslösen können.

Fallback-Strategien

Wenn das zustandsbasierte Styling für die Nutzererfahrung oder das Seitenlayout entscheidend ist, musst du einen Fallback für Browser bereitstellen, die den Selektor :has() nicht unterstützen. Für rein dekorative Effekte kann :has() als progressive Verbesserung ohne Fallback verwendet werden.

ERFORDERLICH: Wenn du ein Fallback für kritische Funktionen implementierst, musst du in deinem CSS @supports not selector(:has(*)) verwenden, um ein herkömmliches klassenbasiertes Fallback zu definieren. Wenn die kritische Statusänderung auf einer Nutzerinteraktion beruht, müssen Sie auch ein kleines Inline-Script mit CSS.supports() verwenden, um diese Klasse basierend auf dem entsprechenden JavaScript-Ereignis (z.B. change, focus, blur) umzuschalten, das die Statusänderung darstellt.

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