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
- Declarar markup inerte com
template. - Usar
slotpara encaixar conteúdo por ocorrência. - Entender Shadow DOM como árvore encapsulada.
- Registrar um elemento customizado simples com JS.
- Ver que o nome do custom element precisa de hífen.
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.

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
- Por que o conteúdo de
templatenão aparece sozinho? - Para que serve um
slotnomeado? - Por que o nome
belavista-avaliacaotem hífen?
Respostas comentadas
- Porque o navegador não renderiza o fragmento até o script clonar e inserir (aqui, no shadow).
- Definir um encaixe específico; o filho com
slot="…"correspondente preenche aquele ponto. - 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);