Pular para o conteúdo
murad.guide

Desenvolvimento Web Integral

Capítulo 15

Template, slot e shadow

2 min de leitura 413 palavras 1 gravura

Componentes reutilizáveis (web components) misturam HTML, um pouco de JavaScript e encapsulamento. No Belavista, imagine um cartão de avaliação de oficina que se repete sem copiar markup à mão.

O exemplo deste capítulo é 15-template-slot-shadow, no pacote que acompanha o volume. Leia → mude uma coisa → preveja → recarregue → confira.

O que você vai aprender

O elemento template

template guarda um fragmento que não é renderizado até o JavaScript clonar e inserir. Útil para UI repetida.

<template id="cartao-oficina">
  <article class="cartao">
    <slot name="titulo"><h2>Oficina</h2></slot>
    <p class="nota">Avaliação do visitante (demo)</p>
  </article>
</template>

Inspecione o HTML: o template existe no documento, mas a tela fica vazia até o script agir.

Slot e conteúdo projetado

slot é o encaixe. Com name, vira slot nomeado. No uso do componente, um filho com slot="titulo" preenche aquele buraco. Se ninguém preencher, o conteúdo padrão dentro do slot aparece.

Template inerte e shadow root encapsulado

Shadow DOM em uma frase

Shadow DOM é uma árvore anexada ao host (o custom element). Estilos e markup internos ficam isolados do resto da página. O HTML “de fora” (light DOM) entra pelos slots. Você precisa de JavaScript para attachShadow e para clonar o template.

Elemento customizado (mínimo)

Nomes de custom elements usam letra minúscula e hífen (belavista-avaliacao). Sem hífen, o navegador não registra como custom element válido.

Erro comum: esperar que o template apareça sozinho na tela, ou esquecer o hífen no nome da tag.

<!DOCTYPE html>
<html lang="pt-BR">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Template, slot e shadow — Belavista</title>
  </head>
  <body>
    <main>
      <h1>Avaliações das oficinas</h1>
      <p>Dois usos do mesmo componente. Mude o texto do slot e recarregue.</p>

      <template id="tpl-avaliacao">
        <style>
          :host { display: block; margin-block: 1rem; padding: 0.75rem; border: 1px solid #333; }
          .estrelas { letter-spacing: 0.15em; }
        </style>
        <article>
          <slot name="titulo"><strong>Oficina</strong></slot>
          <p class="estrelas" part="estrelas">★★★★☆</p>
          <slot>Sem comentário.</slot>
        </article>
      </template>

      <belavista-avaliacao>
        <span slot="titulo">Composição tipográfica</span>
        Bom ritmo e boas perguntas.
      </belavista-avaliacao>

      <belavista-avaliacao>
        <span slot="titulo">Impressão serigráfica</span>
      </belavista-avaliacao>
    </main>
    <script>
      class BelavistaAvaliacao extends HTMLElement {
        connectedCallback() {
          if (this.shadowRoot) return;
          const root = this.attachShadow({ mode: "open" });
          const tpl = document.getElementById("tpl-avaliacao");
          root.append(tpl.content.cloneNode(true));
        }
      }
      customElements.define("belavista-avaliacao", BelavistaAvaliacao);
    </script>
  </body>
</html>

Abra as ferramentas de desenvolvedor e localize o shadow root sob belavista-avaliacao.

Limite deste volume

Não precisa decorar toda a especificação de Custom Elements. Precisa ver: template inerte → clone → shadow → slots. CSS fino de componentes e frameworks ficam para outros cursos. Aqui o foco é HTML + API mínima do navegador.

Atividades

  1. Por que o conteúdo de template não aparece sozinho?
  2. Para que serve um slot nomeado?
  3. Por que o nome belavista-avaliacao tem hífen?

Respostas comentadas

  1. Porque o navegador não renderiza o fragmento até o script clonar e inserir (aqui, no shadow).
  2. Definir um encaixe específico; o filho com slot="…" correspondente preenche aquele ponto.
  3. Custom elements exigem hífen para distinguir de tags HTML nativas.

O arquivo completo

Referência do exemplo 15-template-slot-shadow, reproduzida aqui para que você possa conferir sem sair da página.

componente.js

class CartazBadge extends HTMLElement {
  connectedCallback() {
    const template = document.getElementById("badge-template");
    const shadow = this.attachShadow({ mode: "open" });
    shadow.appendChild(template.content.cloneNode(true));
    const slotTitle = this.getAttribute("titulo") || "Cartaz";
    const titleEl = shadow.querySelector("[data-titulo]");
    if (titleEl) titleEl.textContent = slotTitle;
  }
}
customElements.define("cartaz-badge", CartazBadge);