W przeszłości selektory CSS mogły przechodzić tylko w dół – można było stylizować element podrzędny na podstawie elementu nadrzędnego, ale nie element nadrzędny na podstawie elementu podrzędnego. Pseudoklasa :has() zmienia to, umożliwiając używanie stylów warunkowych dla elementu kontenera w zależności od obecności lub stanu jego elementów podrzędnych.
Łącząc :has() z pseudoklasami stanu (takimi jak :checked, :focus, :valid, :invalid lub :not()), możesz tworzyć złożone, interaktywne komponenty interfejsu użytkownika całkowicie w CSS, bez konieczności używania JavaScriptu do przełączania klas „modyfikatorów” (takich jak .is-active lub .has-error) w elementach nadrzędnych.
Jest to szczególnie przydatne w przypadku komponentów, które muszą reagować na interakcje wewnętrzne, takie jak zlokalizowany przełącznik motywu reagujący na pole wyboru (:checked), grupa formularzy wyróżniająca błąd (:invalid) lub karta, która się podnosi, gdy zaznaczony jest link podrzędny (:focus-within).
Wdrażanie stylów kontenera zależnych od stanu
OBOWIĄZKOWO: musisz użyć selektora :has() w elemencie kontenera, aby wykryć konkretny stan (np. :checked, :focus, :invalid) jego interaktywnego elementu podrzędnego.
Aby utworzyć komponent, który zmienia styl na podstawie stanu elementu podrzędnego:
- Zdefiniuj domyślny styl: ustaw zmienne CSS w kontenerze, aby określić jego stan podstawowy.
- Zastosuj zastąpienia zależne od stanu: kieruj reklamy na kontener za pomocą
:has([child-selector]:[state])i ponownie zdefiniuj zmienne CSS dla stanu aktywnego lub alternatywnego.
Przykład: komponent, który zmienia motyw na podstawie przełącznika podrzędnego.
/* 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>
Wskazówka dotycząca wydajności: gdy używasz :has(), ogranicz selektor do najbardziej konkretnego kontenera (np. .theme-card). Unikaj zakotwiczania go w elementach bardzo wysokiego poziomu, takich jak body:has(...), jeśli zmiany stylu są zlokalizowane, ponieważ szerokie zapytania :has() mogą powodować więcej ponownych obliczeń układu.
Strategie zastępcze
Jeśli stylizacja oparta na stanie jest kluczowa dla wygody użytkownika lub układu strony, musisz zapewnić rozwiązanie zastępcze dla przeglądarek, które nie obsługują selektora :has(). W przypadku efektów czysto dekoracyjnych elementu :has() można używać jako progresywnego ulepszenia bez kreacji zastępczej.
OBOWIĄZKOWE: podczas wdrażania kreacji zastępczej dla funkcji krytycznych musisz użyć @supports not selector(:has(*)) w kodzie CSS, aby zdefiniować tradycyjną kreację zastępczą opartą na klasach. Jeśli zmiana stanu krytycznego zależy od interakcji użytkownika, musisz też użyć małego skryptu wbudowanego z atrybutem CSS.supports(), aby przełączać tę klasę na podstawie odpowiedniego zdarzenia JavaScriptu (np. change, focus, blur) reprezentującego zmianę stanu.
/* 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);
});
}
}