|$ curl https://forge-ai.dev/api/markdown?path=docs/html/template-slot
$cat docs/template-&-slot.md
updated Last week·30 min read·published

Template & Slot

HTMLWeb ComponentsReferenceIntermediate
Introduction

The <template> and <slot> elements are foundational Web Components technologies for declarative content reuse and composition. The <template> element holds inert HTML fragments that can be cloned and activated at runtime, while the <slot> element defines insertion points within those fragments for flexible content projection.

Together, they enable powerful patterns: reusable component templates, dynamic rendering without string concatenation, declarative shadow DOM, and content projection similar to Angular's ng-content or React's children prop — but native to the browser.

info

Unlike a <div> with display: none, a <template> element's content is completely inert — no images load, no scripts execute, no styles apply until the content is cloned and inserted into the DOM.
template Element

The <template> element holds HTML that is not rendered by the browser. Its content is accessible via the content property, which returns a DocumentFragment. To use the template, clone the fragment with cloneNode(true) and insert it into the live DOM.

template-basic.html
HTML
1<!-- Template definition -->
2<template id="user-card">
3 <article class="user-card">
4 <img class="avatar" src="" alt="" width="48" height="48" />
5 <div class="info">
6 <h3 class="name"></h3>
7 <p class="role"></p>
8 </div>
9 </article>
10</template>
11
12<div id="container"></div>
13
14<script>
15 const template = document.getElementById('user-card');
16 const container = document.getElementById('container');
17
18 // Clone the template content
19 const clone = template.content.cloneNode(true);
20
21 // Populate cloned content
22 clone.querySelector('.avatar').src = 'avatar.jpg';
23 clone.querySelector('.avatar').alt = 'User avatar';
24 clone.querySelector('.name').textContent = 'Jane Doe';
25 clone.querySelector('.role').textContent = 'Engineer';
26
27 // Insert into the live DOM
28 container.appendChild(clone);
29</script>

The content property is a read-only DocumentFragment. It is important to note that cloneNode performs a deep clone of the fragment, copying all child nodes, attributes, and text content. The original template remains unchanged and reusable.

template-clone.js
JavaScript
1const tmpl = document.getElementById('my-template');
2
3// Access the inert document fragment
4const fragment = tmpl.content;
5console.log(fragment.nodeType); // 11 (DocumentFragment)
6
7// Clone for insertion (deep clone)
8const instance1 = tmpl.content.cloneNode(true);
9const instance2 = tmpl.content.cloneNode(true);
10
11// Each instance is independent
12instance1.querySelector('h3').textContent = 'Card 1';
13instance2.querySelector('h3').textContent = 'Card 2';
14
15document.body.appendChild(instance1);
16document.body.appendChild(instance2);
17
18// The template remains pristine
19console.log(tmpl.content.querySelector('h3').textContent); // ''

best practice

Always use cloneNode(true) with templates. Using innerHTML on the target element re-parses the HTML string, which is slower and loses event listeners. Templates preserve event listeners and maintain a single source of markup truth.
slot Element

The <slot> element acts as a placeholder inside a shadow tree (or template) that projects light DOM children into specific positions. It enables content composition — the component author defines the structure, while the consumer provides the content.

Default and Named Slots

A slot without a name attribute is the default slot and captures all child nodes not assigned to a named slot. Named slots use the name attribute and match light DOM children that have the corresponding slot attribute.

named-slots.html
HTML
1<template id="page-layout">
2 <style>
3 .layout { display: grid; grid-template-rows: auto 1fr auto; min-height: 200px; }
4 header { background: #111; padding: 16px; border-bottom: 1px solid #333; }
5 main { padding: 16px; }
6 footer { background: #111; padding: 12px 16px; border-top: 1px solid #333; font-size: 12px; color: #666; }
7 </style>
8 <div class="layout">
9 <header><slot name="header">Header</slot></header>
10 <main><slot>Main Content</slot></main>
11 <footer><slot name="footer">Footer</slot></footer>
12 </div>
13</template>
14
15<page-layout>
16 <h1 slot="header">My Page</h1>
17 <p>This is the main content area.</p>
18 <p slot="footer">&copy; 2026 Blueprint Docs</p>
19</page-layout>

Fallback Content

Slot fallback content renders when no light DOM node is assigned to the slot. The fallback lives inside the slot element in the shadow tree and is replaced when the consumer provides content.

slot-fallback.html
HTML
1<template id="tooltip-icon">
2 <style>
3 .tooltip { position: relative; display: inline-flex; cursor: help; }
4 .tip { position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%);
5 background: #333; color: #fff; padding: 4px 8px; border-radius: 4px;
6 font-size: 11px; white-space: nowrap; display: none; }
7 .tooltip:hover .tip { display: block; }
8 </style>
9 <span class="tooltip">
10 <slot name="icon">&#9432;</slot>
11 <span class="tip"><slot>Info</slot></span>
12 </span>
13</template>
14
15<!-- Uses fallback content -->
16<tooltip-icon></tooltip-icon>
17
18<!-- Custom content replaces fallback -->
19<tooltip-icon>
20 <span slot="icon">&#9888;</span>
21 Warning: Disk space low
22</tooltip-icon>
Combining template + slot

The real power emerges when templates and slots are combined inside custom elements with shadow DOM. The template defines the internal structure and styling, while slots provide the content projection API.

template-plus-slot.html
HTML
1<template id="accordion-item">
2 <style>
3 .accordion { border: 1px solid #222; border-radius: 6px; overflow: hidden; margin-bottom: 8px; }
4 .trigger { width: 100%; padding: 12px 16px; background: #111; border: none; color: #00FF41; cursor: pointer; text-align: left; font-size: 13px; display: flex; justify-content: space-between; }
5 .trigger:hover { background: #1A1A1A; }
6 .content { padding: 0 16px; max-height: 0; overflow: hidden; transition: max-height 0.2s; }
7 .content.open { max-height: 200px; padding: 12px 16px; }
8 .arrow { transition: transform 0.2s; }
9 .arrow.open { transform: rotate(180deg); }
10 </style>
11 <div class="accordion">
12 <button class="trigger" part="trigger">
13 <slot name="title">Section</slot>
14 <span class="arrow" part="arrow">&#9660;</span>
15 </button>
16 <div class="content" part="content">
17 <slot></slot>
18 </div>
19 </div>
20</template>
21
22<accordion-item>
23 <span slot="title">What is Shadow DOM?</span>
24 <p>Shadow DOM provides encapsulation for web components.</p>
25</accordion-item>
🔥

pro tip

When combining template + slot in custom elements, create the shadow DOM in the constructor, import the template content, and append it to the shadow root. This pattern is efficient because the template is parsed only once and cloned for each element instance.
Declarative Shadow DOM

Declarative Shadow DOM (DSD) allows you to create shadow roots directly in HTML without JavaScript. Use a <template> with the shadowrootmode attribute set to open or closed. This enables server-side rendering of web components and reduces JavaScript dependency.

declarative-shadow-dom.html
HTML
1<!-- Declarative Shadow DOM — no JS needed -->
2<my-component>
3 <template shadowrootmode="open">
4 <style>
5 :host { display: block; padding: 16px; background: #111;
6 border: 1px solid #333; border-radius: 8px; }
7 h2 { color: #00FF41; margin: 0 0 8px; }
8 slot { color: #A0A0A0; font-size: 13px; }
9 </style>
10 <h2>Declarative Component</h2>
11 <slot>Default content</slot>
12 </template>
13 <p>This content is projected into the slot.</p>
14</my-component>
15
16<script>
17 // DSD polyfill for browsers that don't support it
18 if (!HTMLTemplateElement.prototype.hasOwnProperty('shadowRootMode')) {
19 const supportsDSD = document.currentScript?.getAttribute('shadowrootmode');
20 if (!supportsDSD) {
21 // Apply polyfill (e.g., @webcomponents/template-shadowroot)
22 }
23 }
24</script>

Declarative Shadow DOM is supported in Chromium-based browsers and Safari 16.4+. For Firefox and older browsers, a lightweight polyfill is available from the webcomponents project.

dsd-multiple.html
HTML
1<!-- Multiple declarative shadow components -->
2<style>
3 .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; max-width: 600px; margin: 0 auto; }
4</style>
5<div class="grid">
6 <div>
7 <template shadowrootmode="open">
8 <style> :host { display: block; background: #0A0A0A; border: 1px solid #333; border-radius: 6px; padding: 12px; font-family: system-ui; } h3 { margin: 0 0 4px; color: #00FF41; font-size: 14px; } p { margin: 0; font-size: 12px; color: #808080; } </style>
9 <h3>CPU Usage</h3>
10 <p>Idle — 2% utilization</p>
11 </template>
12 </div>
13 <div>
14 <template shadowrootmode="open">
15 <style> :host { display: block; background: #0A0A0A; border: 1px solid #333; border-radius: 6px; padding: 12px; font-family: system-ui; } h3 { margin: 0 0 4px; color: #00FF41; font-size: 14px; } p { margin: 0; font-size: 12px; color: #808080; } </style>
16 <h3>Memory</h3>
17 <p>6.2 GB / 16 GB used</p>
18 </template>
19 </div>
20</div>

warning

Declarative Shadow DOM requires the shadowrootmode attribute (note: mode not mode spelling). The older shadowroot attribute is deprecated. Always use shadowrootmode="open" for new code.
Template Instantiation

Template instantiation refers to patterns for efficiently creating multiple instances from a single template. The recommended approach is to clone the fragment once per instance, populate it with data, and append it to the DOM. This is significantly faster than parsing innerHTML for each instance.

instantiation.js
JavaScript
1const tmpl = document.getElementById('item-template');
2const list = document.getElementById('list');
3
4const items = [
5 { name: 'Alice', role: 'Developer' },
6 { name: 'Bob', role: 'Designer' },
7 { name: 'Carol', role: 'Manager' },
8];
9
10// Batch create from template — efficient
11const fragment = document.createDocumentFragment();
12
13for (const item of items) {
14 const clone = tmpl.content.cloneNode(true);
15 clone.querySelector('.name').textContent = item.name;
16 clone.querySelector('.role').textContent = item.role;
17 fragment.appendChild(clone);
18}
19
20// Single append for performance
21list.appendChild(fragment);

For very large datasets (thousands of items), consider using a virtual scroller or pagination alongside template cloning. Each clone is a lightweight document fragment, but excessive DOM nodes can still impact rendering performance.

Use Cases

Templates and slots serve a wide range of use cases across web development.

Reusable HTML snippets — define a card template once, clone for each data item
Dynamic rendering — swap template content based on application state without innerHTML
Web component internals — combine template + slot + shadow DOM for complete encapsulation
Declarative Shadow DOM — server-side rendered components with progressive enhancement
Content projection — slot-based layouts where consumers provide header, body, footer content
Stamped patterns — email templates, notification lists, table rows rendered from data arrays
Fragment caching — store cloned fragments in memory for instant re-insertion

Live preview of a template-powered card list with slots:

preview
Best Practices

cloneNode vs innerHTML

Cloning a template with cloneNode(true) is consistently 2-5x faster than parsing the same HTML string with innerHTML. Additionally, cloneNode preserves event listeners bound to elements in the template, while innerHTML requires re-attaching them. For any scenario where you render the same structure multiple times, templates are the clear winner.

ApproachPerformanceEvent ListenersMarkup Source
template + cloneNodeFast — pre-parsed fragmentPreservedSingle source of truth
innerHTMLSlower — re-parses each timeLost — must re-attachString concatenation
createElementModerate — many API callsPreservedImperative code

Fragment Performance

Use document.createDocumentFragment() when appending multiple clones to avoid layout thrashing
Batch DOM reads and writes — avoid interleaving reads with clones
For virtual scrolling, recycle cloned elements instead of creating new ones
Avoid deep template hierarchies — keep templates focused and composable
Use template strings (template literals) for simple cases, template elements for complex structures
Consider using a template engine (like lit-html or htmx) for reactive template binding

best practice

Define templates in the <head> or at the top of <body> so they are available before any script runs. For dynamically created templates, use document.createElement('template') and set its innerHTML — the content remains inert until cloned.
📝

note

Templates and slots are part of the Web Components family alongside Custom Elements and Shadow DOM. The <template> element is well-supported across all browsers. Slots require shadow DOM and are supported in all modern browsers. For best results, use all three technologies together.
$Blueprint — Engineering Documentation·Section ID: HTML-27·Revision: 1.0