The template Element
Store inert HTML that you clone on demand with JavaScript.
The <template> element holds a chunk of markup that the browser parses but does not render or run. Images inside don't load, scripts don't execute — it's a dormant blueprint sitting in the DOM until you decide to use it.
Why it beats string concatenation
Before <template>, people built repeated UI by gluing HTML strings together, which is error-prone and unsafe. A template lets you author the markup normally, then clone it as many times as you need.
The pattern is always the same three steps:
- Grab the template's
.content. - Clone it with
cloneNode(true). - Fill in the clone and append it to the page.
const tpl = document.querySelector('#row');
const copy = tpl.content.cloneNode(true);
list.appendChild(copy);This is the same cloning mechanism that powers list rendering in many frameworks — now you know what's underneath.
Example
When to use it
- A web component uses <template> to define reusable card HTML that JavaScript clones and appends for each data item.
- A table row template is stored in <template> and stamped out in a loop when the server returns JSON data.
- A framework polyfill uses <template> to hold inert HTML that is activated only when the user triggers a modal.
More examples
Template cloned and appended by JS
content.cloneNode(true) copies the inert template; JavaScript fills data before appending it to the DOM.
<template id="card-tpl">
<div class="card">
<h3 class="card-title"></h3>
<p class="card-desc"></p>
</div>
</template>
<div id="card-list"></div>
<script>
const tpl = document.getElementById("card-tpl");
const clone = tpl.content.cloneNode(true);
clone.querySelector(".card-title").textContent = "HTML Basics";
clone.querySelector(".card-desc").textContent = "Learn tags and structure.";
document.getElementById("card-list").appendChild(clone);
</script>Template for repeated list rows
A <template> for a table row is cloned once per data record to build the table body dynamically.
<table>
<thead><tr><th>Name</th><th>Score</th></tr></thead>
<tbody id="scores"></tbody>
</table>
<template id="row-tpl">
<tr><td class="name"></td><td class="score"></td></tr>
</template>
<script>
const data = [{ name: "Alice", score: 95 }, { name: "Bob", score: 88 }];
const tpl = document.getElementById("row-tpl");
const tbody = document.getElementById("scores");
data.forEach(({ name, score }) => {
const row = tpl.content.cloneNode(true);
row.querySelector(".name").textContent = name;
row.querySelector(".score").textContent = score;
tbody.appendChild(row);
});
</script>Template inside a web component
<template> stores the shadow DOM structure and scoped styles; a custom element clones it into its shadow root.
<template id="hello-tpl">
<style>p { color: teal; }</style>
<p>Hello, <slot></slot>!</p>
</template>
<script>
class HelloWorld extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: "open" });
const tpl = document.getElementById("hello-tpl");
shadow.appendChild(tpl.content.cloneNode(true));
}
}
customElements.define("hello-world", HelloWorld);
</script>
<hello-world>SoundsCode</hello-world>
Discussion