The <template> Element
The <template> element holds markup that the browser
parses but does not render, activate, or execute. Its content sits inert
in the document until JavaScript clones it and inserts the clone
somewhere else in the DOM. It is the standard building block for
"stamping out" repeated HTML — table rows, cards, list items, and the
internals of Web Components.
Why <template> Exists
Before <template>, developers stored reusable markup as
a JavaScript string or inside a hidden <div> with
display: none. Both approaches had problems: string markup is
easy to get wrong and awkward to edit, and a hidden <div>
still runs scripts, loads images, and applies CSS — it is just visually
hidden. <template> solves both: it is real, editable
HTML, and its contents are genuinely inactive.
Basic Usage
template-basic.html
<template id="row-template">
<tr>
<td class="name"></td>
<td class="email"></td>
</tr>
</template>
<table id="users">
<tbody></tbody>
</table>
<script>
const template = document.getElementById('row-template')
const tbody = document.querySelector('#users tbody')
const users = [
{ name: 'Ada Lovelace', email: 'ada@example.com' },
{ name: 'Grace Hopper', email: 'grace@example.com' },
]
users.forEach((user) => {
const clone = template.content.cloneNode(true)
clone.querySelector('.name').textContent = user.name
clone.querySelector('.email').textContent = user.email
tbody.appendChild(clone)
})
</script>Rendered table: +----------------+-----------------------+ | Ada Lovelace | ada@example.com | | Grace Hopper | grace@example.com | +----------------+-----------------------+
Content Stays Inert Until Activated
This is the defining feature of <template>. Anything
inside it is parsed as a DOM fragment (accessible via the element's
.content property, a DocumentFragment) but is
never part of the live, rendered document.
<img>elements inside a template do not load their images.<script>elements inside a template do not execute.CSS selectors in the main stylesheet do not match template content (until it is cloned into the real DOM).
document.querySelectoron the main document will not find elements still sitting inside a template.
inert-content.html
<template>
<img src="expensive-image.jpg" alt="Not loaded yet">
<script>console.log('This never runs while inside the template')</script>
</template>
<!-- Nothing above is fetched or executed until someone clones template.content -->.content property, not by treating the <template> element itself as a regular container — .content is the detached DocumentFragment that actually holds the nodes.Use Case: Reusable Row / Card Templates
The most common use of <template> is rendering a list of
items from data — search results, comments, notification cards — without
concatenating HTML strings by hand.
card-template.html
<template id="card-template">
<article class="card">
<h3 class="card-title"></h3>
<p class="card-body"></p>
</article>
</template>
<div id="card-list"></div>
<script>
function renderCard({ title, body }) {
const tpl = document.getElementById('card-template')
const node = tpl.content.cloneNode(true)
node.querySelector('.card-title').textContent = title
node.querySelector('.card-body').textContent = body
document.getElementById('card-list').appendChild(node)
}
renderCard({ title: 'Welcome', body: 'Thanks for signing up!' })
renderCard({ title: 'Reminder', body: 'Your trial ends in 3 days.' })
</script>Use Case: Web Component Internals
Custom Elements very commonly define their internal markup as a
<template> and clone it into a shadow root when the
element is created. This keeps the component's structure declarative and
separate from imperative DOM-building code.
component-template.js
const template = document.createElement('template')
template.innerHTML = `
<style>p { color: teal; }</style>
<p><slot>Default text</slot></p>
`
class GreetingCard extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: 'open' })
shadow.appendChild(template.content.cloneNode(true))
}
}
customElements.define('greeting-card', GreetingCard)<slot>, Custom Elements, and Shadow DOM for how<template> fits into the full Web Components picture.Quick Reference
Concept | Detail |
|---|---|
Rendered by default? | No — content is never displayed as-is |
Access its markup |
|
Clone into the DOM |
|
Scripts inside run? | No, not until cloned into the live document |
Images inside load? | No, not until cloned into the live document |
Typical uses | List/row rendering, Web Component internals |
Use
<template>whenever you find yourself building repeated markup with string concatenation.Always clone
.content, never move or reference the<template>element's children directly.Combine with
<slot>and Shadow DOM when authoring Custom Elements for the cleanest separation of structure and behavior.