Back

Styliser les Web Components avec le Shadow DOM et CSS

Styliser les Web Components avec le Shadow DOM et CSS

Si vous avez déjà intégré un Web Component dans une page et vous êtes demandé pourquoi votre CSS global ne fonctionnait plus à l’intérieur, vous avez rencontré la frontière du shadow. Ce n’est pas un bug — c’est tout l’intérêt. Le Shadow DOM offre aux éléments personnalisés leur propre arbre DOM isolé, ce qui signifie que les styles ne débordent pas, ni vers l’intérieur, ni vers l’extérieur, par défaut. Mais cela ne veut pas dire qu’il est impossible de les styliser. Cela signifie que la stylisation devient intentionnelle.

Cet article aborde les principaux mécanismes de stylisation du Shadow DOM : les styles internes, :host, ::slotted(), les propriétés personnalisées CSS, ::part(), et adoptedStyleSheets.

Points clés à retenir

  • Le Shadow DOM isole les styles par conception, de sorte que le CSS global ne peut pas pénétrer à l’intérieur d’un composant et que les styles internes ne peuvent pas en sortir.
  • Les styles du shadow agissent comme des valeurs par défaut et se placent en dessous des styles rédigés par l’auteur dans la cascade, à la manière de la feuille de style de l’agent utilisateur d’un navigateur.
  • Utilisez :host et :host() pour styliser l’élément du composant, et ::slotted() pour styliser les enfants du light DOM projetés (descendants directs uniquement).
  • Les propriétés personnalisées CSS et ::part() forment une API de stylisation publique délibérée : variables pour les tokens de thème, parts pour la stylisation structurelle.
  • adoptedStyleSheets permet à plusieurs composants de partager un seul CSSStyleSheet analysé, améliorant les performances à grande échelle.

Pourquoi le Shadow DOM change le comportement du CSS

Lorsque vous appelez attachShadow() sur un élément, vous créez un arbre DOM distinct attaché à cet élément (le shadow host). Les sélecteurs de la feuille de style de votre page ne peuvent pas atteindre l’intérieur de cet arbre, et les sélecteurs à l’intérieur de l’arbre du shadow ne peuvent pas atteindre l’extérieur.

Il existe une nuance importante à comprendre : les styles du shadow DOM se placent en dessous de vos styles rédigés dans la cascade CSS. Cela signifie qu’une règle globale comme * { color: red } remplacera une règle :host { color: green } à l’intérieur du shadow — même si la règle globale apparaît plus tôt dans l’ordre du code source. Considérez les styles du shadow comme les styles par défaut du composant, similaires à la façon dont la feuille de style de l’agent utilisateur d’un navigateur fournit des valeurs par défaut pour <button> ou <input>.

Styliser le Shadow Host avec :host

Depuis l’intérieur de l’arbre du shadow, :host sélectionne l’élément auquel le shadow est attaché :

:host {
  display: block;
  font-family: sans-serif;
}

Vous pouvez également utiliser :host(selector) pour appliquer des styles de manière conditionnelle en fonction des attributs ou des classes sur l’hôte :

:host([disabled]) {
  opacity: 0.5;
  pointer-events: none;
}

Comme l’élément hôte vit dans le light DOM, les styles au niveau du document remplaceront les règles :host. Si vous voulez que les valeurs par défaut du composant l’emportent, utilisez !important à l’intérieur du shadow — l’un des rares cas où c’est véritablement approprié.

Styliser le contenu projeté avec ::slotted()

Les slots permettent de projeter du contenu du light DOM dans un arbre de shadow. Le pseudo-élément ::slotted() permet de styliser ces éléments projetés depuis l’intérieur du shadow :

::slotted(p) {
  margin: 0;
  color: #333;
}

Limitation importante : ::slotted() ne cible que l’élément directement assigné à un slot — pas ses descendants. Un sélecteur tel que ::slotted(p span) ne fonctionnera pas, et ::slotted() n’accepte qu’un sélecteur composé (pas de combinateurs de descendants). Pour une stylisation plus profonde, comptez sur l’héritage CSS ou laissez les styles du light DOM s’en occuper.

Les propriétés personnalisées CSS franchissent la frontière du shadow

Les variables CSS (propriétés personnalisées) traversent librement la frontière du shadow. Cela en fait l’outil le plus flexible pour le theming des Web Components :

/* Dans l'arbre du shadow */
:host {
  background: var(--card-bg, white);
  color: var(--card-color, black);
}
/* Dans la feuille de style de votre page */
my-card {
  --card-bg: #1a1a2e;
  --card-color: #eee;
}

Le composant définit les points d’accroche ; le consommateur définit les valeurs. Incluez toujours des valeurs de repli pour que le composant fonctionne sans aucune configuration externe.

Exposer des points de stylisation avec les CSS Shadow Parts (::part())

Les CSS Shadow Parts sont la réponse moderne à la stylisation des éléments internes d’un Web Component depuis l’extérieur. À l’intérieur du composant, vous marquez les éléments avec l’attribut part :

<button part="trigger">Open</button>

À l’extérieur du composant, les consommateurs peuvent cibler directement cette part en utilisant ::part() :

my-dialog::part(trigger) {
  background: royalblue;
  border-radius: 4px;
}

Il s’agit d’une API de stylisation intentionnelle — l’auteur du composant décide de ce qui est exposé. Elle est plus puissante que les variables CSS pour la stylisation structurelle (mise en page, bordures, arrière-plans) tout en gardant les détails d’implémentation internes privés.

Partager efficacement des styles avec adoptedStyleSheets

adoptedStyleSheets permet d’attacher des objets CSSStyleSheet directement à une racine de shadow. Il bénéficie d’un large support sur les navigateurs modernes et convient particulièrement aux bibliothèques de composants qui doivent partager une feuille de style analysée entre de nombreuses instances :

const sheet = new CSSStyleSheet();
sheet.replaceSync(`:host { display: block; }`);

class MyComponent extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    shadow.adoptedStyleSheets = [sheet];
  }
}

customElements.define('my-component', MyComponent);

Le navigateur analyse la feuille de style une seule fois, quel que soit le nombre de composants qui l’utilisent — un gain de performance significatif à grande échelle. Vous pouvez ajouter ou retirer des feuilles de style en modifiant le tableau adoptedStyleSheets, et les modifications apportées à un CSSStyleSheet partagé s’appliquent partout où il est adopté.

Choisir la bonne approche

ObjectifOutil
Styliser l’élément hôte du composant:host / :host()
Styliser le contenu light DOM projeté::slotted()
Exposer des tokens de theming aux consommateursPropriétés personnalisées CSS
Exposer une stylisation structurelle::part()
Partager des styles entre plusieurs composantsadoptedStyleSheets

Conclusion

La stylisation du Shadow DOM ne consiste pas à exclure le CSS — il s’agit de rendre l’encapsulation explicite. Utilisez les styles internes et :host pour les valeurs par défaut, les variables CSS et ::part() comme votre API de stylisation publique, et adoptedStyleSheets lorsque les performances à grande échelle comptent. Une fois ce modèle mental acquis, la stylisation des Web Components devient simple.

FAQ

Parce que le composant utilise le Shadow DOM, qui crée un arbre DOM isolé du document principal. Les feuilles de style de la page ne peuvent pas sélectionner les éléments à l'intérieur de l'arbre du shadow. Pour styliser les éléments internes, l'auteur du composant doit exposer des points d'accroche tels que les propriétés personnalisées CSS ou les CSS Shadow Parts via l'attribut part, que vous ciblez ensuite avec ::part() depuis l'extérieur.

Utilisez les propriétés personnalisées pour les tokens de design comme les couleurs, les espacements et les polices qui se transmettent naturellement par héritage. Utilisez ::part() lorsque les consommateurs ont besoin d'un contrôle structurel sur un élément interne spécifique, comme le remplacement des bordures, des arrière-plans ou de la mise en page sur un bouton ou un en-tête. Les parts offrent un accès plus fin, tandis que les variables restent plus simples et plus larges en portée.

Le pseudo-élément ::slotted() ne cible que les nœuds de premier niveau directement assignés à un slot, pas leurs descendants. Il n'accepte également qu'un sélecteur composé, les combinateurs de descendants sont donc invalides. Pour styliser les enfants des éléments projetés, comptez sur l'héritage CSS depuis l'élément projeté ou laissez la feuille de style du light DOM du consommateur gérer directement ces descendants.

Oui. Il est pris en charge par tous les navigateurs modernes evergreen et constitue la méthode recommandée pour partager des styles entre plusieurs racines de shadow sans réanalyse. Un seul CSSStyleSheet analysé peut être attaché à plusieurs racines de shadow, ce qui réduit la mémoire et améliore le temps de démarrage. Les feuilles de style partagées peuvent également être mises à jour et réutilisées efficacement entre plusieurs composants.

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay