HTMLTemplates (<template>)

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

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.querySelector on the main document will not find elements still sitting inside a template.

inert-content.html

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 -->
Accessing content correctly
Always read a template's markup through its .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

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

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)
Related pages
See the dedicated pages on <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

templateEl.content (a DocumentFragment)

Clone into the DOM

templateEl.content.cloneNode(true)

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.