W3docs

Shadow DOM Slots, Komposition

Slots und Komposition im JavaScript Shadow DOM: Standard- und benannte Slots, Light DOM vs. Shadow DOM, der abgeflachte Baum, das slotchange-Ereignis und assignedNodes/assignedElements mit ausführbaren Beispielen.

Slots und Komposition machen Shadow DOM wirklich wiederverwendbar. Ein Komponentenentwickler schreibt einmal eine feste interne Struktur, und die Nutzer füllen sie mit eigenem Markup — ohne dass die beiden je in Konflikt geraten. Diese Seite behandelt das <slot>-Element (Standard- und benannte Slots), wie Light DOM und Shadow DOM zu einem abgeflachten Baum kombiniert werden, das slotchange-Ereignis sowie die Methoden assignedNodes() / assignedElements(), mit denen man ausliest, was in einem Slot gelandet ist.

Wenn Sie noch nicht mit Shadow DOM vertraut sind, lesen Sie zuerst Shadow DOM für die Grundlagen zu attachShadow() und Shadow Roots sowie Web Components dafür, wie Slots neben benutzerdefinierten Elementen und Templates eingesetzt werden.

Light DOM vs. Shadow DOM

Komposition umfasst zwei Bäume:

  • Light DOM — das Markup, das der Benutzer zwischen den Tags Ihres Elements schreibt: <my-card>...dieser Teil...</my-card>. Es befindet sich im regulären Dokument und verbleibt dort.
  • Shadow DOM — das Markup, das Sie mit attachShadow() anhängen. Es ist gekapselt und vom äußeren Dokument nicht direkt erreichbar.

Ein <slot> ist ein Fenster: Er sitzt im Shadow DOM und projiziert Light-DOM-Kinder hinein. Die Light-DOM-Knoten werden nicht verschoben — sie werden lediglich an der Position des Slots angezeigt. Diese kombinierte Ansicht ist der abgeflachte Baum, und genau diesen rendert und gestaltet der Browser.

Slots im Shadow DOM verstehen

Ein Slot ist ein Platzhalter in Ihrem Shadow DOM, in den der Browser Inhalte aus dem Light DOM einfügt. Slots ermöglichen es einer generischen Komponente, in jeder Instanz anders auszusehen, während sie eine interne Vorlage teilen.

Einen Standard-Slot definieren

Verwenden Sie das <slot>-Element. Ein Slot ohne name-Attribut ist der Standard-Slot: Er fängt alle Light-DOM-Kinder auf, die kein slot-Attribut haben. Text innerhalb von <slot> ist Fallback-Inhalt, der nur angezeigt wird, wenn nichts zugewiesen wurde.

<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>

Das erste <custom-element> zeigt "Default content", da nichts zugewiesen wurde. Das zweite zeigt den fettgedruckten Text — sein Light-DOM-Kind ersetzt den Fallback. Beachten Sie, dass das Markup weiterhin im Dokument verbleibt; der Slot zeigt es nur an.

Benannte Slots

Wenn eine Komponente mehr als einen Einfügepunkt hat, geben Sie jedem <slot> einen name und verknüpfen Sie ihn vom Light DOM aus mit einem slot="..."-Attribut. So leiten Sie den richtigen Inhalt an die richtige Stelle.

<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>

Das <div slot="content"> wird dem <slot name="content"> zugeordnet, sodass "Content from parent" den Fallback ersetzt. Alles, was keinem benannten Slot entspricht, würde in einen Standard-Slot fallen, sofern einer vorhanden ist, oder einfach nicht gerendert werden.

Komposition im Shadow DOM verbessern

Komposition im Kontext von Shadow DOM bedeutet, UI-Komponenten und Inhalte durch Kombination von Slots und ihren verteilten Inhalten zu größeren, wiederverwendbaren Strukturen zusammenzusetzen. Im Kontext von Shadow DOM ermöglicht Komposition die Erstellung hochgradig anpassbarer und wiederverwendbarer Web Components.

Um im Shadow DOM per Slots verteilten Inhalt vom Elternelement aus zu gestalten, verwenden Sie das CSS-Pseudoelement ::slotted() — zum Beispiel ::slotted(div) { color: blue; }. Für das vollständige Bild zu ::slotted(), :host und CSS Custom Properties siehe Shadow DOM Styling.

Komponenten mit Slots zusammensetzen

Eine leistungsstarke Möglichkeit, Komposition zu nutzen, besteht darin, mehrere Slots in ein strukturiertes Layout zu kombinieren. Hier definiert eine zusammengesetzte Komponente Kopf-, Inhalts- und Fußzeilenbereiche:

<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>

Jedes slot="..."-Kind wird an seinen passenden benannten Slot weitergeleitet und erzeugt ein sauberes Kopf-/Inhalts-/Fußzeilen-Layout, das jede Instanz unterschiedlich befüllen kann.

Auf Slot-Änderungen reagieren mit slotchange

Slotted Content ist dynamisch: Ein Nutzer kann Light-DOM-Kinder jederzeit hinzufügen, entfernen oder ersetzen. Das slotchange-Ereignis wird auf einem <slot> ausgelöst, sobald sich seine zugewiesenen Knoten ändern, damit Ihre Komponente reagieren kann — eine Zusammenfassung neu rendern, validieren, lazy-loaden und so weiter. Hören Sie innerhalb der Shadow Root darauf:

<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>

Anfangs zeigt die Komponente "Tabs: 2". Wenn der dritte <button> hinzugefügt wird, löst slotchange aus und die Anzahl aktualisiert sich auf "Tabs: 3".

Slotted Content lesen: assignedNodes() vs. assignedElements()

Beide Methoden werden auf einem <slot> aufgerufen und geben zurück, was der Browser ihm aus dem Light DOM zugewiesen hat:

  • slot.assignedNodes() gibt alle Knoten zurück — Elemente und Textknoten (einschließlich Leerzeichen zwischen Tags).
  • slot.assignedElements() gibt nur Element-Knoten zurück. Das ist in der Regel das, was Sie benötigen.

Übergeben Sie { flatten: true }, um bei verschachtelten Slots in tiefere Ebenen zu gehen, wenn Slots komponentenübergreifend verkettet sind:

// 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 });

Bevorzugen Sie assignedElements(), es sei denn, Sie benötigen speziell die Textknoten; so ersparen Sie sich das Herausfiltern von Leerzeichen.

Der abgeflachte Baum, zusammengefasst

Der Browser verschiebt Light-DOM-Knoten nicht buchstäblich in das Shadow DOM. Stattdessen baut er einen abgeflachten Baum, indem er jeden Slot zum Rendern und Gestalten durch seine zugewiesenen Knoten ersetzt. Praktische Konsequenzen:

  • Slotted Elements verbleiben im Dokument, sodass document.querySelector() sie weiterhin findet und ihre ursprünglichen class-/id-Attribute weiterhin gelten.
  • Sie werden durch das CSS der Seite gestaltet, während die Komponente sie nur über ::slotted() erreichen kann.
  • Ereignis-Listener, die im Light DOM angehängt wurden, funktionieren weiterhin — Ereignisse steigen durch den abgeflachten Baum auf.

Fazit

Slots und Komposition machen aus einem gekapselten Shadow DOM eine flexible, wiederverwendbare Komponente: Sie definieren die Struktur, und die Nutzer liefern den Inhalt über Standard- und benannte Slots. Denken Sie an die wichtigsten Konzepte — Light DOM vs. Shadow DOM, der abgeflachte Baum, den der Browser rendert, das slotchange-Ereignis zum Reagieren auf Änderungen und assignedElements() zum Auslesen des Slotted Content.

Um tiefer einzusteigen, siehe Web Components für das große Bild, Custom Elements für den Elementlebenszyklus, Shadow DOM Styling für ::slotted() und :host sowie Shadow DOM für die Grundlagen.

Übung

Übung
Wofür werden Slots im JavaScript Shadow DOM verwendet?
Wofür werden Slots im JavaScript Shadow DOM verwendet?
Was this page helpful?