In passato, i selettori CSS potevano solo scorrere verso il basso: potevi applicare uno stile a un elemento secondario in base al relativo elemento principale, ma non a un elemento principale in base al relativo elemento secondario. La pseudoclasse :has() cambia questa situazione, consentendoti di applicare uno stile condizionale a un elemento contenitore in base alla presenza o allo stato dei suoi discendenti.
Combinando :has() con pseudo-classi di stato (come :checked, :focus, :valid, :invalid o :not()), puoi creare componenti UI complessi e interattivi interamente in CSS, senza bisogno di JavaScript per attivare/disattivare le classi "modificatore" (come .is-active o .has-error) sugli elementi principali.
Ciò è particolarmente utile per i componenti che devono rispondere alle interazioni interne, ad esempio un pulsante di attivazione/disattivazione del tema localizzato che reagisce a una casella di controllo (:checked), un gruppo di moduli che evidenzia un errore (:invalid) o una scheda che si espande quando viene attivato un link secondario (:focus-within).
Implementazione dello stile dei container in base allo stato
OBBLIGATORIO: devi utilizzare il selettore :has() sull'elemento contenitore per rilevare lo stato specifico (ad es. :checked, :focus, :invalid) del relativo elemento secondario interattivo.
Per creare un componente che modifica lo stile in base allo stato di un elemento secondario:
- Definisci lo stile predefinito: imposta le variabili CSS sul contenitore per definirne lo stato di base.
- Applica override basati sullo stato: scegli come target il contenitore con
:has([child-selector]:[state])e ridefinisci le variabili CSS per lo stato attivo o alternativo.
Esempio: un componente che cambia tema in base a un pulsante di attivazione/disattivazione secondario.
/* 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>
Suggerimento per il rendimento: quando utilizzi :has(), limita l'ambito del selettore al container più specifico possibile (ad esempio .theme-card). Evita di ancorarlo a elementi di livello molto elevato come body:has(...) se le modifiche allo stile sono localizzate, poiché le query :has() ampie possono attivare più ricalcoli del layout.
Strategie di riserva
Se lo stile basato sullo stato è fondamentale per l'esperienza utente o il layout della pagina, devi fornire un'alternativa per i browser che non supportano il selettore :has(). Per effetti puramente decorativi, :has() può essere utilizzato come potenziamento progressivo senza un comportamento di riserva.
OBBLIGATORIO: quando implementi un comportamento di riserva per le funzionalità critiche, devi utilizzare @supports not selector(:has(*)) nel CSS per definire un comportamento di riserva tradizionale basato su classi. Se la modifica dello stato critico si basa sull'interazione dell'utente, devi anche utilizzare un piccolo script incorporato con CSS.supports() per attivare/disattivare la classe in base all'evento JavaScript equivalente (ad es. change, focus, blur) che rappresenta la modifica dello stato.
/* 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);
});
}
}