W3docs

Slots et composition dans le Shadow DOM

Apprenez les slots et la composition dans le Shadow DOM JavaScript : slots par défaut et nommés, light DOM, arbre aplati, événement slotchange et assignedNodes/assignedElements.

Les slots et la composition sont ce qui rend le Shadow DOM véritablement réutilisable. Un auteur de composant écrit une structure interne fixe une seule fois, et les utilisateurs la remplissent avec leur propre balisage — sans que les deux entrent jamais en collision. Cette page couvre l'élément <slot> (slots par défaut et nommés), la façon dont le light DOM et le shadow DOM se combinent en un arbre aplati, l'événement slotchange, ainsi que les méthodes assignedNodes() / assignedElements() que vous utilisez pour lire ce qui a été placé dans un slot.

Si vous débutez avec le Shadow DOM, lisez d'abord Shadow DOM pour les bases d'attachShadow() et des racines shadow, et Web Components pour comprendre comment les slots s'intègrent aux éléments personnalisés et aux templates.

Light DOM vs. shadow DOM

La composition implique deux arbres :

  • Light DOM — le balisage que l'utilisateur écrit entre les balises de votre élément : <my-card>...cette partie...</my-card>. Il vit dans le document ordinaire et y reste.
  • Shadow DOM — le balisage que vous attachez avec attachShadow(). Il est encapsulé et n'est pas directement accessible depuis le document extérieur.

Un <slot> est une fenêtre : il se trouve dans le shadow DOM et projette les enfants du light DOM dans celui-ci. Les nœuds du light DOM ne sont pas déplacés — ils sont seulement affichés à la position du slot. Cette vue combinée est l'arbre aplati, et c'est ce que le navigateur rend et stylise réellement.

Comprendre les slots dans le Shadow DOM

Un slot est un emplacement réservé dans votre shadow DOM où le navigateur dépose le contenu fourni depuis le light DOM. Les slots permettent à un composant générique de donner un aspect différent à chaque instance tout en partageant un même template interne.

Définir un slot par défaut

Utilisez l'élément <slot>. Un slot sans attribut name est le slot par défaut : il capture tout enfant du light DOM ne possédant pas d'attribut slot. Le texte à l'intérieur de <slot> est le contenu de secours, affiché uniquement lorsqu'aucun contenu n'est assigné.

<body>
  <script>
    class CustomElement extends HTMLElement {
      constructor() {
        super();
        const shadowRoot = this.attachShadow({ mode: 'open' });
        shadowRoot.innerHTML = `
          <div class="container">
            <slot>Default content</slot>
          </div>
        `;
      }
    }
    
    customElements.define('custom-element', CustomElement);
  </script>

  <!-- No children: the slot shows its fallback, "Default content" -->
  <custom-element></custom-element>

  <!-- Children with no slot attribute go into the default slot -->
  <custom-element><strong>Hello from the light DOM!</strong></custom-element>
</body>

Le premier <custom-element> affiche "Default content" car rien n'a été assigné. Le second affiche le texte en gras — son enfant du light DOM remplace le contenu de secours. Notez que le balisage reste dans le document ; le slot ne fait que l'afficher.

Slots nommés

Lorsqu'un composant possède plus d'un point d'insertion, donnez à chaque <slot> un name et faites-y référence depuis le light DOM avec un attribut slot="...". C'est ainsi que vous acheminez le bon contenu au bon endroit.

<body>
  <!-- Define Custom Element -->
  <script>
    // Define Custom Element Class
    class CustomElement extends HTMLElement {
      constructor() {
        super();
        const shadowRoot = this.attachShadow({ mode: 'open' });
        shadowRoot.innerHTML = `
          <style>
            /* Define styles for the component */
            .container {
              border: 1px solid #ccc;
              padding: 20px;
            }
          </style>
          <div class="container">
            <slot name="content">Default content</slot>
          </div>
        `;
      }
    }

    // Define Custom Element
    customElements.define('custom-element', CustomElement);
  </script>

  <!-- Displaying the custom element -->
  <custom-element>
    <div slot="content">Content from parent</div>
  </custom-element>
</body>

Le <div slot="content"> est associé à <slot name="content">, de sorte que "Content from parent" remplace le contenu de secours. Tout ce qui ne correspond à aucun slot nommé serait acheminé vers un slot par défaut, s'il en existe un, ou simplement ne serait pas rendu.

Améliorer la composition dans le Shadow DOM

La composition dans le contexte du Shadow DOM désigne l'assemblage de composants d'interface et de contenu en combinant des slots et leur contenu distribué pour créer des structures plus complexes et réutilisables. Appliquée dans le contexte du Shadow DOM, la composition permet la création de web components hautement personnalisables et réutilisables.

Pour styliser le contenu distribué dans les slots depuis le parent, utilisez le pseudo-élément CSS ::slotted() — par exemple, ::slotted(div) { color: blue; }. Consultez Shadow DOM Styling pour une vue complète de ::slotted(), :host et des propriétés personnalisées CSS.

Composer des composants avec des slots

L'une des façons les plus puissantes de tirer parti de la composition est de combiner plusieurs slots dans une mise en page structurée. Ici, un composant composite définit des régions d'en-tête, de contenu et de pied de page :

<body>
  <script>
    // Define Composite Element Class
    class CompositeElement extends HTMLElement {
      constructor() {
        super();
        const shadowRoot = this.attachShadow({ mode: 'open' });
        shadowRoot.innerHTML = `
          <style>
            /* Define styles for the composite component */
            .container {
              border: 1px solid #ccc;
              padding: 20px;
            }
          </style>
          <div class="container">
            <slot name="header"></slot>
            <slot name="content"></slot>
            <slot name="footer"></slot>
          </div>
        `;
      }
    }

    // Define Composite Element
    customElements.define('composite-element', CompositeElement);
  </script>
  <composite-element>
    <div slot="header">Header</div>
    <div slot="content">Content</div>
    <div slot="footer">Footer</div>
  </composite-element>
</body>

Chaque enfant slot="..." est acheminé vers son slot nommé correspondant, produisant une mise en page propre en-tête/contenu/pied de page que chaque instance peut remplir différemment.

Réagir aux changements de slot avec slotchange

Le contenu sloté est dynamique : un consommateur peut ajouter, supprimer ou remplacer des enfants du light DOM à tout moment. L'événement slotchange se déclenche sur un <slot> chaque fois que ses nœuds assignés changent, de sorte que votre composant peut réagir — re-rendre un résumé, valider, charger à la demande, etc. Écoutez-le depuis l'intérieur de la racine shadow :

<body>
  <script>
    class TabList extends HTMLElement {
      constructor() {
        super();
        const root = this.attachShadow({ mode: 'open' });
        root.innerHTML = `<p id="count"></p><slot></slot>`;
        this._slot = root.querySelector('slot');
        this._count = root.querySelector('#count');
      }

      connectedCallback() {
        this._slot.addEventListener('slotchange', () => this.update());
        this.update();
      }

      update() {
        // assignedElements() returns only element nodes in the slot
        const items = this._slot.assignedElements();
        this._count.textContent = `Tabs: ${items.length}`;
      }
    }

    customElements.define('tab-list', TabList);
  </script>

  <tab-list>
    <button>One</button>
    <button>Two</button>
  </tab-list>
  <script>
    // Adding a child later fires slotchange → count updates to 3
    const list = document.querySelector('tab-list');
    const extra = document.createElement('button');
    extra.textContent = 'Three';
    list.appendChild(extra);
  </script>
</body>

Initialement, le composant affiche "Tabs: 2". Lorsque le troisième <button> est ajouté, slotchange se déclenche et le compteur se met à jour à "Tabs: 3".

Lire le contenu sloté : assignedNodes() vs. assignedElements()

Les deux méthodes sont appelées sur un <slot> et retournent ce que le navigateur lui a assigné depuis le light DOM :

  • slot.assignedNodes() retourne tous les nœuds — les éléments et les nœuds texte (y compris les espaces entre les balises).
  • slot.assignedElements() retourne uniquement les nœuds élément. C'est généralement ce dont vous avez besoin.

Passez { flatten: true } pour descendre dans les slots imbriqués lorsque les slots sont chaînés entre composants :

// All nodes, including stray text/whitespace nodes
slot.assignedNodes();           // e.g. [text, <button>, text, <button>, text]

// Elements only — cleaner for iteration
slot.assignedElements();        // e.g. [<button>, <button>]

// Flatten through nested <slot> assignments
slot.assignedElements({ flatten: true });

Préférez assignedElements() sauf si vous avez spécifiquement besoin des nœuds texte ; cela vous évite de filtrer les espaces blancs.

L'arbre aplati, récapitulatif

Le navigateur ne déplace pas littéralement les nœuds du light DOM dans le shadow DOM. Il construit plutôt un arbre aplati en substituant chaque slot par ses nœuds assignés pour le rendu et la mise en forme. Conséquences pratiques :

  • Les éléments slotés restent dans le document, donc document.querySelector() les trouve toujours et leurs class/id originaux s'appliquent toujours.
  • Ils sont stylisés par le CSS de la page, tandis que le composant ne les atteint que via ::slotted().
  • Les écouteurs d'événements attachés dans le light DOM continuent de fonctionner — les événements se propagent à travers l'arbre aplati.

Conclusion

Les slots et la composition transforment un shadow DOM encapsulé en un composant flexible et réutilisable : vous définissez la structure, et les consommateurs fournissent le contenu via des slots par défaut et des slots nommés. Retenez les éléments clés — light DOM vs. shadow DOM, l'arbre aplati que le navigateur rend, l'événement slotchange pour réagir aux changements, et assignedElements() pour lire ce qui a été sloté.

Pour aller plus loin, consultez Web Components pour une vue d'ensemble, Custom Elements pour le cycle de vie des éléments, Shadow DOM Styling pour ::slotted() et :host, et Shadow DOM pour les fondamentaux.

Pratique

Pratique
À quoi servent les slots dans le Shadow DOM JavaScript ?
À quoi servent les slots dans le Shadow DOM JavaScript ?
Was this page helpful?