Noms CSS définis par l'auteur et Shadow DOM: dans les spécifications et dans la pratique

Les noms CSS définis par les auteurs et le Shadow DOM sont censés fonctionner ensemble. Toutefois, les navigateurs ne respectent pas la spécification, parfois les uns les autres, et chaque nom CSS est incohérent d'une manière légèrement différente.

Cet article décrit l'état actuel du comportement des noms CSS définis par l'auteur dans les portées d'ombre, dans l'espoir qu'il puisse servir de guide pour améliorer l'interopérabilité dans un avenir proche.

Que sont les noms CSS définis par l'auteur ?

Les noms CSS définis par l'auteur sont un mécanisme de syntaxe CSS relativement ancien, introduit à l'origine pour la règle @keyframes, qui définit un <keyframe-name> comme un identifiant personnalisé ou une chaîne. L'objectif de ce concept est de déclarer quelque chose dans une partie d'une feuille de style, puis de s'y référer dans une autre partie.

/* "fade-in" is a CSS name, representing a set of keyframes */
@keyframes fade-in {
  from { opacity: 0 };
  to { opacity: 1 }
}

.card {
  /* "fade-in" is a reference to the above keyframes */
  animation-name: fade-in;
}

Les autres fonctionnalités CSS qui utilisent des noms CSS sont les polices, les déclarations de propriétés, les requêtes de conteneur et, plus récemment, les transitions de vue, le positionnement des ancres et les animations basées sur le défilement. Le tableau suivant, non exhaustif, inclut les noms dont l'état est vérifié par Chrome.

Fonctionnalité Déclaration de nom Référence de nom
Images clés @keyframes animation-name
Polices @font-face { }
@font-palette-values
font-family
font-palette
Déclarations de propriétés @property
Toute déclaration de propriété personnalisée non enregistrée
var()
Afficher les transitions view-transition-name
view-transition-class
Éléments pseudo ::view-transition-*
Positionnement des ancres anchor-name position-anchor
Animation liée au défilement view-timeline-name
scroll-timeline-name
animation-timeline
Styles de liste @counter-style list-style
Compteurs counter-reset
counter-set
counter-increment
Requêtes de conteneur container-name @container
Page page @page

Comme vous pouvez le voir dans le tableau, un nom CSS possède généralement une référence CSS correspondante. Par exemple, animation-name fait référence au nom @keyframes. Les noms CSS sont différents des noms définis dans le DOM, tels que les attributs et les noms de balises, car ils sont déclarés, puis référencés dans le contexte des feuilles de style.

Lien entre les noms et le Shadow DOM

Alors que les noms CSS sont conçus pour créer des relations entre différentes parties d'un document ou d'une feuille de style, le DOM ombragé est conçu pour faire le contraire. Il encapsule les relations afin d'éviter qu'elles ne fuient entre les composants Web censés avoir leur propre espace de noms.

En combinant les noms CSS et le DOM ombragé, l'expérience de composition des composants Web doit être suffisamment expressive pour être flexible, mais suffisamment limitée pour être stable.

C'est une bonne théorie. En pratique, les navigateurs ne sont pas cohérents dans la façon dont les noms CSS interagissent avec le Shadow DOM, à la fois entre les fonctionnalités du même navigateur, entre les navigateurs et entre les fonctionnalités et les spécifications.

Fonctionnement des noms et du DOM ombragé

Pour comprendre le problème, il est utile de comprendre comment ces parties du CSS doivent fonctionner ensemble en théorie.

Règle générale

La règle générale concernant le comportement des noms CSS dans les arbres d'ombre est définie dans la spécification CSS Scoping Level 1. Pour résumer: un nom CSS est global dans le champ d'application dans lequel il est défini, ce qui signifie qu'il est accessible à partir des arbres d'ombre descendants, mais pas à partir des arbres d'ombre frères ou ancêtres. Notez que cela diffère des noms utilisés sur la plate-forme Web, tels que les ID d'élément, qui sont encapsulés dans le même champ d'application d'arborescence.

Exception à la règle: @property

Contrairement aux autres noms CSS, les propriétés CSS ne sont pas encapsulées par le Shadow DOM. Il s'agit plutôt du moyen courant de transmettre des paramètres entre différents arbres d'ombre. Cela rend le descripteur @property spécial: il est censé se comporter comme une déclaration de type globale du document qui définit le comportement d'une propriété nommée particulière. Étant donné que les propriétés doivent correspondre entre les arbres d'ombre, une non-correspondance entre les déclarations de propriétés créerait des résultats inattendus. Par conséquent, les déclarations @property sont spécifiées pour être aplaties et résolues en fonction de l'ordre des documents.

Fonctionnement de la règle avec ::part

Les parties d'ombre exposent un élément dans un arbre d'ombre à son arbre parent. L'arborescence parente peut ainsi accéder à cet élément et lui appliquer un style à l'aide de l'élément ::part.

Étant donné que ::part permet à deux portées d'arborescence de styliser le même élément, l'ordre en cascade suivant est spécifié:

  1. Commencez par vérifier le style dans le contexte de l'ombre. Il s'agit du style "par défaut" de la pièce.
  2. Appliquez ensuite le style externe tel que défini dans ::part. Il s'agit du style "personnalisé" de la pièce.
  3. Appliquez ensuite tout style interne défini avec !important. Cela permet à un élément personnalisé de déclarer qu'une certaine propriété d'une certaine partie n'est pas personnalisable par ::part.

Cela signifie que les noms du Shadow DOM ne peuvent pas être référencés à partir d'un ::part, car le ::part est un style de portée hôte plutôt qu'un style de portée "shadow". Exemple :

// inside the shadow DOM:
@keyframes fade-in {
  from { opacity: 0}
}

// This shouldn't work!
// The host style shouldn't know the name "fade-in"
::part(slider) {
  animation-name: fade-in;  
}

Fonctionnement de la règle avec les styles intégrés

Contrairement à ::part, les styles intégrés avec l'attribut style ou ceux qui définissent le style de manière programmatique à l'aide d'un script sont limités à l'étendue de l'élément. En effet, pour appliquer un style à un élément, vous devez accéder à la poignée de l'élément, et donc à la racine de l'ombre elle-même.

Comment les noms CSS et le Shadow DOM fonctionnent ensemble en réalité

Bien que les règles précédentes soient bien définies et cohérentes, les implémentations actuelles ne le reflètent pas toujours. En pratique, @property fonctionne différemment de la spécification de manière cohérente entre les navigateurs, et la plupart des autres fonctionnalités comportent des bugs ouverts (certains d'entre eux n'ont pas encore été publiés, il est donc encore temps de les corriger).

Pour tester et montrer comment ces fonctionnalités fonctionnent en pratique, nous avons créé la page suivante : https://css-names-in-the-shadow.glitch.me/. Cette page comporte plusieurs iFrames, chacun axé sur l'une des fonctionnalités et testant six scénarios:

  • Référence externe à un nom externe: aucun Shadow DOM n'est impliqué, cela devrait fonctionner.
  • Référence externe à un nom interne: cela ne devrait pas fonctionner, car cela signifierait que le nom défini dans le contexte Shadow a été divulgué.
  • Référence interne au nom externe: cela devrait fonctionner, car les noms à l'échelle de l'arborescence sont hérités par les racines fantômes.
  • Référence interne au nom interne: cela devrait fonctionner, car le nom de la référence se trouve dans le même champ d'application.
  • Référence ::part au nom externe: cela devrait fonctionner, car ::part et le nom sont déclarés dans le même champ d'application.
  • Référence ::part au nom interne: cela ne devrait pas fonctionner, car le champ d'application externe ne doit pas connaître les noms déclarés dans le DOM fantôme.

@keyframes

Comme indiqué dans la spécification, vous devriez pouvoir faire référence aux noms de clés d'animation à partir d'une racine d'ombre, à condition que la règle at-rule @keyframes se trouve dans un champ d'application d'ancêtre. En pratique, aucun navigateur ne met en œuvre ce comportement, et les définitions d'images clés ne peuvent être référencées que dans le champ d'application dans lequel elles sont définies. Consultez le problème 10540.

@property

Comme défini dans la spécification, toute déclaration de @property sera aplatie dans le champ d'application du document. Cependant, aujourd'hui, dans tous les navigateurs, vous ne pouvez déclarer @property que dans le champ d'application du document, et les déclarations @property dans les racines d'ombre sont ignorées.
Consultez le problème 10541.

Bugs spécifiques au navigateur

Les autres fonctionnalités ne présentent pas un comportement cohérent entre les navigateurs:

  • @font-face est aplati au niveau du champ d'application racine dans Safari.
  • Chromium n'autorise pas l'héritage des règles anchor-name dans un nœud racine fantôme
  • Les champs d'application de scroll-timeline-name et view-timeline-name ne sont pas correctement définis sur ::part (également dans Chromium).
  • Aucun navigateur n'autorise la déclaration de @font-palette-values dans des racines d'ombre.
  • view-transition-class peut être défini dans une racine d'ombre (la transition elle-même se trouve en dehors de la racine d'ombre).
  • Firefox permet à ::part d'accéder aux noms d'ombres internes (requêtes de conteneur, images clés).
  • Firefox et Safari ne respectent pas @counter-style dans un root d'ombre.

Notez que counter-reset, counter-set et counter-increment ont des règles légèrement différentes, car il s'agit de noms implicites. De plus, les propriétés CSS déclarées disposent d'un ensemble de règles établi et bien testé.

Conclusion

Malheureusement, lorsque vous examinez l'instantané de l'état d'interopérabilité actuel pour les noms CSS et le Shadow DOM, l'expérience est incohérente et génère des bugs. Aucune des fonctionnalités que nous avons examinées ici ne se comporte de manière cohérente d'un navigateur à l'autre et conformément aux spécifications. La bonne nouvelle, c'est que le delta permettant de rendre l'expérience cohérente est une liste limitée de bugs et de problèmes de spécifications. Résolvons à présent ce problème. En attendant, nous espérons que cet aperçu vous aidera si vous rencontrez des difficultés avec les incohérences décrites dans cet article.