Réactivité des jetons de conception

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

Contexte et présentation

Un auteur doit souvent apporter des modifications contextuelles à la conception d'un composant. Auparavant, les auteurs devaient utiliser des sélecteurs pour appliquer de telles modifications. Cela signifiait souvent que, même si de nombreux jetons de conception pouvaient exister en tant que propriétés personnalisées, les jetons de conception d'ordre supérieur ne pouvaient être encodés que sous la forme d'un modèle de sélecteur (c'est-à-dire en utilisant une convention de nom de classe ou d'attribut) ou en tant que props/contexte dans un framework JavaScript.

Les requêtes de style de conteneur permettent aux auteurs de styliser des éléments en fonction de la valeur de propriété personnalisée calculée d'un élément ancêtre. Cela signifie que les auteurs peuvent écrire des valeurs de jeton de conception significatives dans leurs feuilles de style plutôt que de s'appuyer sur le balisage ou JavaScript pour la présentation.

Mise en œuvre

L'implémentation d'un jeton de conception réactif à l'aide d'une requête de style de conteneur est assez simple :

  1. Définissez le jeton de conception d'ordre supérieur en tant que propriété personnalisée sur un conteneur. Il n'est pas nécessaire qu'il s'agisse d'une propriété personnalisée enregistrée.
  2. Utilisez la règle @container style() pour interroger la valeur de cette propriété personnalisée.
  3. Appliquez les styles appropriés à un élément descendant dans le conteneur.

Quelques points à retenir :

  • Le conteneur interrogé avec une requête de style n'a pas besoin d'avoir un container-type ou un container-name défini. Toutefois, un container-name peut permettre d'effectuer des requêtes plus spécifiques.
  • Le conteneur lui-même ne peut pas être stylisé par la requête de style de conteneur.

Voici un exemple de base des étapes d'implémentation ci-dessus.

<div class="features">
  <div class="card"></div>
  <div class="card"></div>
</div>
<div class="bugs">
  <div class="card"></div>
  <div class="card"></div>
  <div class="card"></div>
  <div class="card"></div>
</div>
.features {
  --density: spacious;
}

.bugs {
  --density: compact;
}

@container style(--density: compact) {
  .card {
    padding: 8px;
  }
}

@container style(--density: spacious) {
  .card {
    padding: 24px;
  }
}

Stratégies de remplacement

Tant que les requêtes de style de conteneur ne sont pas prises en charge par Baseline, il n'est PAS RECOMMANDÉ de les utiliser pour les fonctionnalités essentielles qui doivent être disponibles sur tous les navigateurs, car il n'est pas facile de créer une solution de remplacement qui ne supprime pas leurs avantages ou qui n'ait pas ses propres limites. Par exemple, si une préférence utilisateur concernant la densité de l'UI n'est pas considérée comme une fonctionnalité essentielle qui doit être disponible dans toutes les expériences, les requêtes de style de conteneur peuvent être utilisées pour implémenter la fonctionnalité sans solution de remplacement.

Utiliser des sélecteurs à la place

Pour les fonctionnalités essentielles, il convient d'utiliser une autre approche basée sur des sélecteurs. Cet exemple utilise un attribut data-density pour encoder le jeton de conception de densité dans le balisage plutôt que comme propriété personnalisée :

<div class="features" data-density="spacious">
  <div class="card"></div>
  <div class="card"></div>
</div>
<div class="bugs" data-density="compact">
  <div class="card"></div>
  <div class="card"></div>
  <div class="card"></div>
  <div class="card"></div>
</div>
/* This example uses `:where()` to avoid increasing specificity */
:where([data-density="compact"]) .card {
  padding: var(--card-padding-compact);
}

:where([data-density="spacious"]) .card {
  padding: var(--card-padding-spacious);
}

L'une des principales limites de cette approche de remplacement est qu'elle n'est pas compatible avec l'imbrication d'éléments avec l'attribut data-density défini. Étant donné que la spécificité du sélecteur est la même, l'ordre d'apparition sera utilisé pour déterminer les styles (c'est-à-dire que [data-density="spacious"] aura toujours la priorité sur [data-density="compact"]).

Utiliser des requêtes de style comme amélioration progressive

Bien que cela ne soit PAS RECOMMANDÉ, si vous souhaitez utiliser des requêtes de style comme amélioration progressive pour une fonctionnalité essentielle, vous pouvez créer des propriétés personnalisées, puis inclure les requêtes de style pour éviter les doublons. Assurez-vous que l'approche de remplacement utilise :where() lors de la sélection des éléments de conteneur pour éviter d'augmenter la spécificité.

.card {
  --card-padding-compact: 8px;
  --card-padding-spacious: 24px;
}

:where([data-density="compact"]) .card {
  padding: var(--card-padding-compact);
}

:where([data-density="spacious"]) .card {
  padding: var(--card-padding-spacious);
}

/* Use style queries as a progressive enhancement: same specificity, so order of appearance is used */

@container style(--density: compact) {
  .card {
    padding: var(--card-padding-compact);
  }
}

@container style(--density: spacious) {
  .card {
    padding: var(--card-padding-spacious);
  }
}

Vérification des fonctionnalités avec CSS uniquement

Si vous devez vérifier la compatibilité des requêtes de style de conteneur pour afficher de manière conditionnelle une UI qui en dépend, vous pouvez utiliser une requête de style :

:root {
  --style-queries-supported: check;
}

.density-toggle {
  display: none;
}

@container style(--style-queries-supported) {
  .density-toggle {
    display: revert;
  }
}

Vérification des fonctionnalités avec JavaScript

Pour la vérification des fonctionnalités avec JavaScript, c'est un peu plus compliqué, car l'interface CSSContainerRule existait avant l'ajout des requêtes de style de conteneur. Elle n'est donc pas fiable à cette fin. Vous devez plutôt vérifier la valeur calculée d'une propriété connue définie avec une requête de style.

Cet exemple utilise une propriété personnalisée, car elle n'aura aucun effet visuel :

:root {
  --style-queries-supported: check;
}

@container style(--style-queries-supported: check) {
  body {
    --style-queries-supported: yes;
  }
}

Vérifiez ensuite la valeur calculée en JavaScript comme suit :

if (getComputedStyle(document.body).getPropertyValue("--style-queries-supported") === "yes") {
  // Use container style queries
} else {
  // Use fallback strategy
}