W3docs

Slots e Composição no Shadow DOM

Aprenda slots e composição no Shadow DOM do JavaScript: slots padrão e nomeados, light DOM vs. shadow DOM, a árvore achatada, o evento slotchange e assignedNodes/assignedElements, com exemplos de custom elements executáveis.

Slots e composição são o que torna o Shadow DOM genuinamente reutilizável. O autor de um componente escreve uma estrutura interna fixa uma vez, e os consumidores a preenchem com sua própria marcação — sem que as duas jamais colidam. Esta página aborda o elemento <slot> (slots padrão e nomeados), como o light DOM e o shadow DOM se combinam em uma árvore achatada, o evento slotchange e os métodos assignedNodes() / assignedElements() usados para ler o que chegou em um slot.

Se você é novo no Shadow DOM, leia Shadow DOM primeiro para os fundamentos de attachShadow() e raízes de sombra, e Web Components para entender como os slots se encaixam ao lado de custom elements e templates.

Light DOM vs. shadow DOM

A composição envolve duas árvores:

  • Light DOM — a marcação que o usuário escreve entre as tags do seu elemento: <my-card>...esta parte...</my-card>. Ela vive no documento normal e permanece lá.
  • Shadow DOM — a marcação que você anexa com attachShadow(). Ela é encapsulada e não é diretamente acessível a partir do documento externo.

Um <slot> é uma janela: ele fica no shadow DOM e projeta os filhos do light DOM para dentro dele. Os nós do light DOM não são movidos — eles apenas são exibidos na posição do slot. Essa visão combinada é a árvore achatada, e é o que o navegador de fato renderiza e estiliza.

Entendendo slots no Shadow DOM

Um slot é um marcador de posição no seu shadow DOM onde o navegador deposita o conteúdo fornecido pelo light DOM. Os slots são a forma como um componente genérico permite que cada instância tenha aparência diferente enquanto compartilha um único template interno.

Definindo um slot padrão

Use o elemento <slot>. Um slot sem atributo name é o slot padrão: ele captura qualquer filho do light DOM que não tenha atributo slot. O texto dentro de <slot> é o conteúdo de fallback, exibido apenas quando nada é atribuído.

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

O primeiro <custom-element> renderiza "Default content" porque nada foi atribuído. O segundo renderiza o texto em negrito — seu filho do light DOM substitui o fallback. Note que a marcação ainda vive no documento; o slot apenas a exibe.

Slots nomeados

Quando um componente tem mais de um ponto de inserção, dê a cada <slot> um name e faça a correspondência a partir do light DOM com um atributo slot="...". É assim que você direciona o conteúdo certo para o lugar certo.

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

O <div slot="content"> é correspondido ao <slot name="content">, então "Content from parent" substitui o fallback. Qualquer coisa que não corresponda a um slot nomeado cairia para um slot padrão, se houver, ou simplesmente não seria renderizada.

Aprimorando a composição no Shadow DOM

Composição, no contexto do Shadow DOM, refere-se à montagem de componentes de UI e conteúdo combinando slots e seu conteúdo distribuído para criar estruturas mais complexas e reutilizáveis. Quando aplicada dentro do contexto do Shadow DOM, a composição permite a criação de web components altamente personalizáveis e reutilizáveis.

Para estilizar o conteúdo distribuído nos slots a partir do componente pai, use o pseudo-elemento CSS ::slotted() — por exemplo, ::slotted(div) { color: blue; }. Consulte Shadow DOM Styling para uma visão completa de ::slotted(), :host e propriedades personalizadas CSS.

Compondo componentes com slots

Uma forma poderosa de aproveitar a composição é combinar múltiplos slots em um layout estruturado. Aqui, um componente composto define regiões de cabeçalho, conteúdo e rodapé:

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

Cada filho slot="..." é direcionado ao seu slot nomeado correspondente, produzindo um layout limpo de cabeçalho/conteúdo/rodapé que qualquer instância pode preencher de forma diferente.

Reagindo a mudanças de slot com slotchange

O conteúdo inserido em slots é dinâmico: um consumidor pode adicionar, remover ou substituir filhos do light DOM a qualquer momento. O evento slotchange é disparado em um <slot> sempre que seus nós atribuídos mudam, para que seu componente possa reagir — re-renderizar um resumo, validar, fazer carregamento lazy, e assim por diante. Ouça por ele a partir de dentro do 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>

Inicialmente, o componente exibe "Tabs: 2". Quando o terceiro <button> é adicionado, slotchange é disparado e a contagem é atualizada para "Tabs: 3".

Lendo conteúdo inserido em slots: assignedNodes() vs. assignedElements()

Ambos os métodos são chamados em um <slot> e retornam o que o navegador atribuiu a ele a partir do light DOM:

  • slot.assignedNodes() retorna todos os nós — elementos e nós de texto (incluindo espaços em branco entre tags).
  • slot.assignedElements() retorna apenas nós de elemento. Isso geralmente é o que você quer.

Passe { flatten: true } para descer em slots aninhados quando os slots estão encadeados entre componentes:

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

Prefira assignedElements() a menos que você precise especificamente dos nós de texto; isso evita que você precise filtrar espaços em branco.

A árvore achatada, recapitulada

O navegador não move literalmente os nós do light DOM para dentro do shadow DOM. Em vez disso, ele constrói uma árvore achatada substituindo cada slot pelos seus nós atribuídos para renderização e estilização. Consequências práticas:

  • Os elementos inseridos em slots permanecem no documento, portanto document.querySelector() ainda os encontra e seus class/id originais ainda se aplicam.
  • Eles são estilizados pelo CSS da página, enquanto o componente os alcança apenas via ::slotted().
  • Os ouvintes de eventos anexados no light DOM continuam funcionando — os eventos se propagam pela árvore achatada.

Conclusão

Slots e composição transformam um shadow DOM encapsulado em um componente flexível e reutilizável: você define a estrutura, e os consumidores fornecem o conteúdo por meio de slots padrão e nomeados. Lembre-se dos elementos-chave — light DOM vs. shadow DOM, a árvore achatada que o navegador renderiza, o evento slotchange para reagir a mudanças e assignedElements() para ler o que foi inserido nos slots.

Para ir mais longe, consulte Web Components para uma visão mais ampla, Custom Elements para o ciclo de vida dos elementos, Shadow DOM Styling para ::slotted() e :host, e Shadow DOM para os fundamentos.

Prática

Prática
Para que são usados os slots no Shadow DOM do JavaScript?
Para que são usados os slots no Shadow DOM do JavaScript?
Was this page helpful?