template Element
HTML template holds inert DOM clones — stamp client-side fragments, pair with slots/web components, and avoid XSS when filling them.
- html
- template
The <template> element holds HTML that is parsed but not rendered and not active (scripts don’t run, images don’t load) until you clone its contents into a live document. It’s the platform’s lightweight client-side partial.
Docs: MDN <template>, DocumentFragment.
Basic stamp pattern
<template id="row-template">
<tr>
<td class="name"></td>
<td class="amount"></td>
<td><button type="button" class="remove">Remove</button></td>
</tr>
</template>
<table>
<tbody id="rows"></tbody>
</table>
const tpl = document.querySelector("#row-template");
const tbody = document.querySelector("#rows");
function addRow({ name, amount }) {
const node = tpl.content.cloneNode(true);
node.querySelector(".name").textContent = name;
node.querySelector(".amount").textContent = amount;
node.querySelector(".remove").addEventListener("click", (e) => {
e.target.closest("tr")?.remove();
});
tbody.append(node);
}
Key points:
- Use
template.content(aDocumentFragment), not the template element itself. cloneNode(true)every time — don’t move the original fragment nodes once.- Fill with
textContent/ safe APIs, not rawinnerHTMLwith user strings.
Why not just a hidden div?
<template> |
Hidden <div> |
|
|---|---|---|
| Active resources | Inert until stamped | May load images/scripts |
| In document flow | Not rendered | Occupies accessibility considerations if not careful |
| Semantics | Explicit inert fragment | Easy to forget |
Web components
Templates pair with Shadow DOM:
<template id="user-card">
<style>
:host {
display: block;
border: 1px solid #ddd;
padding: 0.75rem;
}
</style>
<h2 part="title"></h2>
<slot></slot>
</template>
customElements.define(
"user-card",
class extends HTMLElement {
connectedCallback() {
if (this.shadowRoot) return;
const root = this.attachShadow({ mode: "open" });
root.append(document.querySelector("#user-card").content.cloneNode(true));
root.querySelector("h2").textContent = this.getAttribute("name") ?? "";
}
}
);
Frameworks vs template
React/Vue/Svelte own templating. Native <template> still appears in:
- Design-system docs with vanilla demos
- Progressive enhancement without a bundler
- Web components
- Server-rendered HTML with small client sprinkles
Don’t mix competing renderers on the same subtree without clear ownership.
XSS
// Bad
node.querySelector(".name").innerHTML = user.name;
// Good
node.querySelector(".name").textContent = user.name;
Cloning a template doesn’t sanitize data you insert later.
Interview out-loud
“<template> stores inert HTML. I clone template.content, fill safe text, and append the fragment. It’s better than hidden live DOM for partials and pairs with web components. Frameworks replace many use cases, but the element remains useful for progressive enhancement.”
Footguns
- Appending the fragment once and wondering why the second stamp is empty — clone each time.
- Querying inside template without
.content. - Scripts inside template don’t run until moved — then may run; know the rules.
- Using template IDs that collide across partials.
- innerHTML with untrusted data after clone.
Event binding note
Cloned nodes do not copy event listeners from live prototypes unless you re-bind after clone (as in the stamp example). For many rows, use event delegation on tbody instead of per-row listeners:
tbody.addEventListener("click", (e) => {
const btn = e.target.closest("button.remove");
if (btn) btn.closest("tr")?.remove();
});
Templates then only need structure and text slots — behavior lives once on the parent.
Streaming HTML note
Some frameworks stream HTML with declarative shadow DOM and templates. Understand that template content is still inert until upgraded. Do not assume SEO crawlers execute your template stamping JS — critical content should exist as live HTML for public pages. Use templates for repeated client widgets, not for the only copy of primary content.
Related
Further reading
Related guides
- Accessibility Tree OverviewHow browsers build the accessibility tree from DOM and CSS — roles, names, states, what’s pruned, and how to inspect it in DevTools.
- Audio and Video ElementsNative audio/video — controls, sources, captions, autoplay policies, and accessibility requirements for media on the web.
- Autocomplete and Name Attributesname and autocomplete on form fields — password managers, autofill tokens, and why missing names break real users more than demos.
- Base Element and Relative URLsHow <base href> rewrites relative URLs for links, scripts, and forms — powerful for static hosts, dangerous when set accidentally.
- contenteditable Basicscontenteditable surfaces — what the browser gives you, sanitization, keyboard and a11y gaps, and when to pick a real editor library.