HTML Template and Slot Elements

โ–ถ Try It Yourself

HTML Template and Slot Elements

1. Introduction

The <template> element holds client-side content that is not rendered on page load but can be cloned and inserted into the document via JavaScript. Paired with <slot> elements inside Web Components’ Shadow DOM, it forms the foundation of the native HTML component model. Understanding templates is essential for working with Web Components and for any pattern that requires efficient, repeated DOM creation without string concatenation.

2. Concept

Template vs Other Dynamic Content Methods

Method Parsed on load? Rendered on load? Best For
<template> Yes (HTML parsed) No (inert) Reusable DOM fragments; Web Components
innerHTML string No (raw string) On assignment Simple dynamic content (XSS risk)
createElement API N/A On append Programmatic, type-safe DOM building
Framework templates (JSX, Vue) Compiled Framework-managed Component-driven apps
Note: Content inside <template> is completely inert โ€” images don’t load, scripts don’t execute, and styles don’t apply โ€” until the content is cloned and inserted into the document. This makes templates very efficient for defining reusable structures.
Tip: Use template.content.cloneNode(true) to get a deep clone of the template’s DocumentFragment. Pass true to clone all descendants. Then populate data before appending to the DOM to avoid causing multiple reflows.
Warning: Unlike innerHTML, template cloning is not vulnerable to XSS injection from the template definition itself. However, when you populate the clone with user-supplied data, always use textContent (not innerHTML) to avoid creating XSS vulnerabilities.

3. Basic Example

<!DOCTYPE html>
<html lang="en">
  <head><meta charset="UTF-8"><title>Template Demo</title></head>
  <body>

    <!-- Template: inert until cloned -->
    <template id="product-card-tpl">
      <article class="card">
        <img class="card__img" src="" alt="" width="300" height="200" loading="lazy">
        <div class="card__body">
          <h3 class="card__title"></h3>
          <p class="card__price"></p>
          <button class="card__btn" type="button">Add to Cart</button>
        </div>
      </article>
    </template>

    <!-- Container where cards are inserted -->
    <div id="product-grid"></div>

    <script>
      var products = [
        { id: 'PB15',  name: 'ProBook 15',    price: 'ยฃ1,299', img: '/probook.jpg',  alt: 'ProBook 15 laptop'     },
        { id: 'M4K',   name: '4K Monitor',    price: 'ยฃ249',   img: '/monitor.jpg',  alt: '32-inch 4K monitor'    },
        { id: 'KBD',   name: 'Mech Keyboard', price: 'ยฃ89',    img: '/keyboard.jpg', alt: 'Mechanical keyboard'   },
      ];

      var template = document.getElementById('product-card-tpl');
      var grid     = document.getElementById('product-grid');

      products.forEach(function(p) {
        // Clone the template content
        var clone = template.content.cloneNode(true);

        // Populate โ€” using textContent, NOT innerHTML
        clone.querySelector('.card__img').src = p.img;
        clone.querySelector('.card__img').alt = p.alt;
        clone.querySelector('.card__title').textContent = p.name;
        clone.querySelector('.card__price').textContent = p.price;
        clone.querySelector('.card__btn').dataset.productId = p.id;

        grid.appendChild(clone);
      });
    </script>

  </body>
</html>

4. How It Works

Step 1 โ€” The template Element is Inert

The browser parses the HTML inside <template> into a DocumentFragment accessible via template.content, but nothing is rendered. Images don’t load, scripts don’t run. This is significantly more efficient than hiding content with CSS.

Step 2 โ€” cloneNode(true)

template.content.cloneNode(true) creates a deep copy of the DocumentFragment including all descendants. Each clone is independent โ€” modifying one does not affect others. You can create as many instances as needed from one template definition.

Step 3 โ€” Populate Before Appending

Populate the clone’s elements with data before calling appendChild. This batches all DOM mutations before the browser reflows, improving performance. Setting src on an <img> only triggers a network request after the element is in the document.

Step 4 โ€” slot in Shadow DOM

When used with Web Components, <slot name="title"> inside a shadow template acts as a placeholder. Light DOM children with slot="title" on the host element are projected into the matching slot, combining the component’s template with consumer-provided content.

5. Real-World Example

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Comment Thread</title>
    <style>
      .comment { border: 1px solid #e5e7eb; border-radius: 8px; padding: 1rem; margin-bottom: 1rem; }
      .comment__avatar { width: 40px; height: 40px; border-radius: 50%; vertical-align: middle; margin-right: 0.5rem; }
      .comment__meta { font-size: 0.8rem; color: #6b7280; }
    </style>
  </head>
  <body>
    <h1>Comments</h1>

    <template id="comment-tpl">
      <article class="comment">
        <header>
          <img class="comment__avatar" src="" alt="" width="40" height="40">
          <strong class="comment__author"></strong>
          <time class="comment__meta"></time>
        </header>
        <p class="comment__body"></p>
        <button class="comment__reply" type="button">Reply</button>
      </article>
    </template>

    <section id="comment-list" aria-label="Comments"></section>

    <script>
      var comments = [
        { author: 'Alice', avatar: 'https://via.placeholder.com/40', date: '2025-03-10', dateDisplay: 'Mar 10', body: 'Great tutorial! Very clear explanations throughout.' },
        { author: 'Bob',   avatar: 'https://via.placeholder.com/40', date: '2025-03-11', dateDisplay: 'Mar 11', body: 'The code examples are exactly what I needed. Bookmarked.' },
        { author: 'Carol', avatar: 'https://via.placeholder.com/40', date: '2025-03-12', dateDisplay: 'Mar 12', body: 'Could you cover CSS Grid in the next lesson?' },
      ];

      var tpl  = document.getElementById('comment-tpl');
      var list = document.getElementById('comment-list');

      comments.forEach(function(c) {
        var clone = tpl.content.cloneNode(true);
        clone.querySelector('.comment__avatar').src = c.avatar;
        clone.querySelector('.comment__avatar').alt = c.author + ' avatar';
        clone.querySelector('.comment__author').textContent = c.author;
        var time = clone.querySelector('.comment__meta');
        time.setAttribute('datetime', c.date);
        time.textContent = c.dateDisplay;
        clone.querySelector('.comment__body').textContent = c.body;
        clone.querySelector('.comment__reply').dataset.author = c.author;
        list.appendChild(clone);
      });
    </script>
  </body>
</html>

6. Common Mistakes

Using innerHTML to insert user data (XSS vulnerability)

<script>
  clone.querySelector('.title').innerHTML = userInput;  // XSS risk
</script>

Always use textContent for user-supplied data

<script>
  clone.querySelector('.title').textContent = userInput;  // safe
</script>

Not using cloneNode โ€” all variables point to the same fragment

<script>
  var frag = template.content;
  list.appendChild(frag);  // fragment is moved, template is now empty
  list.appendChild(frag);  // appends nothing โ€” frag is already moved
</script>

Always clone the template content before appending

<script>
  list.appendChild(template.content.cloneNode(true));
  list.appendChild(template.content.cloneNode(true));
</script>

7. Try It Yourself

▶ Try It Yourself

8. Quick Reference

API / Element Purpose Notes
<template id> Defines inert reusable DOM fragment Not rendered; not downloaded (images)
template.content DocumentFragment containing template DOM Read-only; clone before use
content.cloneNode(true) Deep clone of template Independent copy; safe to modify
el.textContent = val Set text safely No XSS risk; use for all user data
parent.appendChild(clone) Insert clone into DOM Triggers rendering and image load
<slot name> Placeholder in shadow DOM template Filled by light DOM slot=”name” children

9. Quiz

🧠 Test Yourself

What happens to images inside a <template> element before it is cloned and inserted?





โ–ถ Try It Yourself