Combinaisons de sélecteurs et de règles @

Fonctionnalités Web : Requêtes de style de conteneur

Problème

Nous devons souvent combiner des critères de filtrage de différents types, par exemple des sélecteurs et des requêtes média, des requêtes média et des requêtes de conteneur, etc.

Par exemple, en combinant (prefers-color-scheme: dark) avec un sélecteur qui cible les forçages de jeu de couleurs (par exemple, :root:has(> head > meta[name="color-scheme"][content="dark"]) ou :root.dark) pour appliquer certains styles en mode clair/sombre, quelle que soit la façon dont le jeu de couleurs a été défini.

L'approche naïve entraîne une duplication du code :

/* Light mode styles */
.card.featured {
  font-weight: 400;
}

#logo-dark {
  display: none;
}

@media (prefers-color-scheme: dark) {
  /* Dark mode styles */
  :root:not(.light) {
    .card.featured {
      font-weight: 300;
    }

    #logo-dark {
      display: inline;
    }

    #logo {
      display: none;
    }
  }
}

/* Dark mode styles, again */
:root.dark {
  .card.featured {
    font-weight: 300;
  }

  #logo-dark {
    display: inline;
  }

  #logo {
    display: none;
  }
}

Que faire à la place ?

Stockez le résultat du filtre combiné dans une propriété personnalisée, puis utilisez des requêtes de style de conteneur pour définir des règles basées sur cette propriété.

IMPORTANT : En raison du fonctionnement des requêtes de conteneur, cette technique ne fonctionne que si nous pouvons définir la propriété personnalisée sur un ancêtre de toutes les règles qui doivent être mises en correspondance.

1. Dissocier la logique de sélection des règles appliquées

:root {
  --color-scheme: light;

  @media (prefers-color-scheme: dark) {
    --color-scheme: dark;
  }

  &:has(> head > meta[name="color-scheme"][content="dark"]) {
    --color-scheme: dark;
  }

  &:has(> head > meta[name="color-scheme"][content="light"]) {
    --color-scheme: light;
  }
}

2. Clé de la propriété personnalisée pour les règles appliquées

@container not style(--color-scheme: dark) {
  .card.featured {
    font-weight: 400;
  }

  #logo-dark {
    display: none;
  }
}

@container style(--color-scheme: dark) {
  .card.featured {
    font-weight: 300;
  }

  #logo {
    display: none;
  }
}

3. FACULTATIF : Classes utilitaires pour activer/désactiver la visibilité

Un cas très courant consiste à activer ou désactiver la visibilité selon que les critères correspondent ou non. Les classes utilitaires doivent l'emporter sur les styles de composants arbitraires (d'où !important) et leur display affiché est inconnu. Elles ne peuvent donc pas utiliser de forçages. Contrôlez-les avec not style(...) :

@container not style(--color-scheme: dark) {
  .if-dark {
    display: none !important;
  }
}

@container not style(--color-scheme: light) {
  .if-light {
    display: none !important;
  }
}

Créations de remplacement

Il existe deux types de solutions de remplacement, selon l'importance de l'embranchement et ce qui constitue une solution de remplacement acceptable.

  1. Pour les embranchements critiques, où tous les états doivent fonctionner correctement même si les requêtes de style ne sont pas prises en charge, vous ne pouvez tout simplement pas utiliser de requêtes de style. Utilisez plutôt l'approche naïve ci-dessus, en dupliquant les règles pour chaque état. Les classes utilitaires peuvent toujours être utilisées, à condition qu'elles soient également définies de cette manière.
  2. Pour les branchements non critiques, où un état par défaut est acceptable, vous pouvez définir les styles de base en dehors de toute règle @container et n'utiliser les requêtes de style que pour les remplacements. Cela préserve l'essentiel de l'esprit de cette approche, son principal inconvénient étant le couplage des valeurs introduit par les remplacements.
#logo-dark {
  display: none;
}

@container style(--color-scheme: dark) {
  #logo-dark {
    display: inline; /* override */
  }

  #logo {
    display: none;
  }
}