Docs
25.3-HTML-Templates
25.3 HTML Templates
Overview
HTML Templates provide a way to define reusable markup that is not rendered until activated. The <template> element holds content that can be cloned and inserted into the DOM.
Learning Objectives
- •Understand the
<template>element - •Clone and activate template content
- •Use templates with Web Components
- •Work with slots for content projection
- •Build reusable template patterns
The Template Element
Basic Template
<!-- Template is not rendered -->
<template id="my-template">
<style>
.card {
padding: 20px;
border: 1px solid #ccc;
}
</style>
<div class="card">
<h2>Template Content</h2>
<p>This won't appear until activated</p>
</div>
</template>
Template Properties
const template = document.getElementById('my-template');
// Check if it's a template
console.log(template instanceof HTMLTemplateElement); // true
// Access the content (DocumentFragment)
console.log(template.content);
// Template content is not part of the document
console.log(template.content.children); // HTMLCollection
Using Templates
Cloning and Inserting
const template = document.getElementById('my-template');
// Clone the template content (deep clone)
const clone = template.content.cloneNode(true);
// Modify the clone before inserting
clone.querySelector('h2').textContent = 'New Title';
// Insert into the document
document.body.appendChild(clone);
Multiple Instances
const template = document.getElementById('card-template');
const container = document.getElementById('container');
const items = ['Item 1', 'Item 2', 'Item 3'];
items.forEach((item) => {
const clone = template.content.cloneNode(true);
clone.querySelector('.title').textContent = item;
container.appendChild(clone);
});
Templates with Web Components
Using Templates in Custom Elements
// Define template
const template = document.createElement('template');
template.innerHTML = `
<style>
:host {
display: block;
padding: 16px;
}
.container {
background: #f5f5f5;
border-radius: 8px;
padding: 20px;
}
</style>
<div class="container">
<slot></slot>
</div>
`;
class MyComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
}
customElements.define('my-component', MyComponent);
Inline Template Reference
<template id="user-card-template">
<style>
.user-card {
display: flex;
align-items: center;
gap: 12px;
}
.avatar {
width: 48px;
height: 48px;
border-radius: 50%;
}
</style>
<div class="user-card">
<img class="avatar" src="" alt="" />
<div class="info">
<h3 class="name"></h3>
<p class="email"></p>
</div>
</div>
</template>
<script>
class UserCard extends HTMLElement {
connectedCallback() {
const template = document.getElementById('user-card-template');
const shadow = this.attachShadow({ mode: 'open' });
shadow.appendChild(template.content.cloneNode(true));
// Populate from attributes
shadow.querySelector('.avatar').src = this.getAttribute('avatar');
shadow.querySelector('.name').textContent = this.getAttribute('name');
shadow.querySelector('.email').textContent = this.getAttribute('email');
}
}
customElements.define('user-card', UserCard);
</script>
Slots in Templates
Default Slot
<template id="card-template">
<div class="card">
<slot>Default content</slot>
</div>
</template>
Named Slots
<template id="article-template">
<article>
<header>
<slot name="title">Untitled</slot>
</header>
<main>
<slot>No content</slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</article>
</template>
<my-article>
<h1 slot="title">Article Title</h1>
<p>Main content paragraph</p>
<span slot="footer">Posted today</span>
</my-article>
Slot Fallback Content
const template = document.createElement('template');
template.innerHTML = `
<div class="notification">
<span class="icon">
<slot name="icon">ℹ️</slot>
</span>
<div class="content">
<slot>Default message</slot>
</div>
</div>
`;
Dynamic Templates
Creating Templates Programmatically
function createTemplate(html) {
const template = document.createElement('template');
template.innerHTML = html;
return template;
}
const cardTemplate = createTemplate(`
<div class="card">
<h3 class="title"></h3>
<p class="description"></p>
</div>
`);
Template Factory Pattern
class TemplateFactory {
static templates = new Map();
static register(name, html) {
const template = document.createElement('template');
template.innerHTML = html;
this.templates.set(name, template);
return template;
}
static get(name) {
return this.templates.get(name);
}
static create(name, data = {}) {
const template = this.get(name);
if (!template) throw new Error(`Template ${name} not found`);
const clone = template.content.cloneNode(true);
// Apply data to clone
for (const [key, value] of Object.entries(data)) {
const element = clone.querySelector(`[data-bind="${key}"]`);
if (element) {
element.textContent = value;
}
}
return clone;
}
}
// Usage
TemplateFactory.register(
'user',
`
<div class="user">
<span data-bind="name"></span>
<span data-bind="email"></span>
</div>
`
);
const userElement = TemplateFactory.create('user', {
name: 'John Doe',
email: 'john@example.com',
});
Template Best Practices
- •Cache templates - Don't query for templates repeatedly
- •Clone deeply - Always use
cloneNode(true) - •Modify before inserting - Update clones before adding to DOM
- •Use slots for composition - Let consumers provide content
- •Keep templates simple - Complex logic belongs in JavaScript
Browser Support
// Check for template support
if ('content' in document.createElement('template')) {
console.log('Templates supported');
} else {
console.log('Templates not supported - use polyfill');
}
Summary
| Feature | Description |
|---|---|
<template> | Container for inactive markup |
.content | DocumentFragment with template contents |
.cloneNode(true) | Create deep copy of template |
<slot> | Placeholder for projected content |
slot="name" | Target specific named slot |