W3docs

Shadow DOM Slots e Composizione

Impara gli slot e la composizione nel Shadow DOM di JavaScript: slot predefiniti e con nome, light DOM vs. shadow DOM, albero appiattito, evento slotchange e assignedNodes/assignedElements.

Gli slot e la composizione sono ciò che rende lo Shadow DOM genuinamente riutilizzabile. L'autore di un componente scrive una struttura interna fissa una volta sola, e i consumatori la riempiono con il proprio markup — senza che i due collidano mai. Questa pagina tratta l'elemento <slot> (slot predefiniti e con nome), come il light DOM e lo shadow DOM si combinano in un albero appiattito, l'evento slotchange, e i metodi assignedNodes() / assignedElements() utilizzati per leggere ciò che è stato inserito in uno slot.

Se sei nuovo allo Shadow DOM, leggi prima Shadow DOM per le basi di attachShadow() e delle shadow root, e Web Components per capire come gli slot si integrano con gli elementi personalizzati e i template.

Light DOM vs. shadow DOM

La composizione coinvolge due alberi:

  • Light DOM — il markup che l'utente scrive tra i tag del tuo elemento: <my-card>...questa parte...</my-card>. Risiede nel documento normale e rimane lì.
  • Shadow DOM — il markup che tu colleghi con attachShadow(). È incapsulato e non direttamente raggiungibile dal documento esterno.

Un <slot> è una finestra: si trova nello shadow DOM e proietta i figli del light DOM al suo interno. I nodi del light DOM non vengono spostati — vengono solo visualizzati nella posizione dello slot. Questa vista combinata è l'albero appiattito, ed è ciò che il browser effettivamente renderizza e applica agli stili.

Capire gli slot nello Shadow DOM

Uno slot è un segnaposto nel tuo shadow DOM in cui il browser inserisce il contenuto fornito dal light DOM. Gli slot sono il modo in cui un componente generico permette a ogni istanza di apparire diversa pur condividendo un unico template interno.

Definire uno slot predefinito

Usa l'elemento <slot>. Uno slot senza attributo name è lo slot predefinito: cattura qualsiasi figlio del light DOM che non ha un attributo slot. Il testo all'interno di <slot> è il contenuto di fallback, mostrato solo quando non viene assegnato nulla.

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

Il primo <custom-element> mostra "Default content" perché non è stato assegnato nulla. Il secondo mostra il testo in grassetto — il suo figlio del light DOM sostituisce il fallback. Si noti che il markup rimane nel documento; lo slot lo visualizza soltanto.

Slot con nome

Quando un componente ha più di un punto di inserimento, assegna un name a ogni <slot> e abbinalo dal light DOM con un attributo slot="...". È così che si instrada il contenuto corretto nel posto giusto.

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

Il <div slot="content"> viene abbinato a <slot name="content">, quindi "Content from parent" sostituisce il fallback. Qualsiasi cosa che non corrisponda a uno slot con nome passerebbe a uno slot predefinito, se esiste, oppure semplicemente non verrebbe renderizzata.

Migliorare la composizione nello Shadow DOM

La composizione nel contesto dello Shadow DOM si riferisce all'assemblaggio di componenti UI e contenuti combinando slot e il loro contenuto distribuito per creare strutture più complesse e riutilizzabili. Applicata nel contesto dello Shadow DOM, la composizione consente la creazione di web component altamente personalizzabili e riutilizzabili.

Per applicare stili al contenuto distribuito negli slot dal genitore, usa lo pseudo-elemento CSS ::slotted() — ad esempio, ::slotted(div) { color: blue; }. Vedi Shadow DOM Styling per il quadro completo di ::slotted(), :host e le proprietà personalizzate CSS.

Comporre componenti con gli slot

Un modo potente per sfruttare la composizione è combinare più slot in un layout strutturato. Qui un componente composito definisce le regioni header, content e footer:

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

Ogni figlio con slot="..." viene instradato al corrispondente slot con nome, producendo un layout pulito header/content/footer che ogni istanza può popolare diversamente.

Reagire ai cambiamenti degli slot con slotchange

Il contenuto slottato è dinamico: un consumatore può aggiungere, rimuovere o sostituire i figli del light DOM in qualsiasi momento. L'evento slotchange si attiva su un <slot> ogni volta che cambiano i suoi nodi assegnati, così il tuo componente può reagire — ridisegnare un riepilogo, validare, effettuare lazy-load e così via. Ascoltalo dall'interno della shadow root:

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

Inizialmente il componente mostra "Tabs: 2". Quando viene aggiunto il terzo <button>, slotchange si attiva e il contatore si aggiorna a "Tabs: 3".

Leggere il contenuto slottato: assignedNodes() vs. assignedElements()

Entrambi i metodi vengono chiamati su un <slot> e restituiscono ciò che il browser gli ha assegnato dal light DOM:

  • slot.assignedNodes() restituisce tutti i nodi — elementi e nodi di testo (inclusi gli spazi bianchi tra i tag).
  • slot.assignedElements() restituisce solo i nodi elemento. Di solito è questo ciò di cui hai bisogno.

Passa { flatten: true } per scendere negli slot annidati quando gli slot sono concatenati tra componenti:

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

Preferisci assignedElements() a meno che tu non abbia specificamente bisogno dei nodi di testo; ti evita di filtrare gli spazi bianchi.

L'albero appiattito, riepilogato

Il browser non sposta letteralmente i nodi del light DOM nello shadow DOM. Invece costruisce un albero appiattito sostituendo ogni slot con i suoi nodi assegnati per la renderizzazione e lo stile. Conseguenze pratiche:

  • Gli elementi slottati rimangono nel documento, quindi document.querySelector() li trova ancora e i loro class/id originali si applicano ancora.
  • Vengono stilizzati dal CSS della pagina, mentre il componente li raggiunge solo tramite ::slotted().
  • I listener di eventi collegati nel light DOM continuano a funzionare — gli eventi si propagano attraverso l'albero appiattito.

Conclusione

Gli slot e la composizione trasformano uno shadow DOM incapsulato in un componente flessibile e riutilizzabile: tu definisci la struttura, e i consumatori forniscono il contenuto tramite slot predefiniti e con nome. Ricorda gli elementi chiave — light DOM vs. shadow DOM, l'albero appiattito che il browser renderizza, l'evento slotchange per reagire ai cambiamenti, e assignedElements() per leggere ciò che è stato slottato.

Per approfondire, vedi Web Components per il quadro generale, Custom Elements per il ciclo di vita degli elementi, Shadow DOM Styling per ::slotted() e :host, e Shadow DOM per i fondamentali.

Pratica

Pratica
A cosa servono gli slot nel Shadow DOM di JavaScript?
A cosa servono gli slot nel Shadow DOM di JavaScript?
Was this page helpful?