Advanced20 min read

Template & Web Components

Learn how to use the template element for reusable HTML fragments, and get an introduction to Shadow DOM and custom elements for building web components.

The <template> Element

The <template> element holds HTML content that is not rendered when the page loads. It serves as a blueprint that you can clone and insert into the DOM with JavaScript.

html
<template id="card-template">
  <div class="card">
    <h3 class="card-title"></h3>
    <p class="card-body"></p>
  </div>
</template>

Key characteristics:

  • Content inside <template> is inert — images do not load, scripts do not run, styles do not apply.
  • The content lives in a DocumentFragment accessible via template.content.
  • You clone it with template.content.cloneNode(true) to create a live copy.

This is much cleaner than building DOM elements entirely in JavaScript or using innerHTML.

Using Templates in JavaScript

Here is the pattern for using a template:

js
// 1. Get the template
const template = document.getElementById('card-template');

// 2. Clone the content
const clone = template.content.cloneNode(true);

// 3. Fill in the data
clone.querySelector('.card-title').textContent = 'My Card';
clone.querySelector('.card-body').textContent = 'Card description here.';

// 4. Append to the DOM
document.getElementById('card-container').appendChild(clone);

You can clone the template as many times as you need — each clone is independent. This is how you build dynamic lists, card grids, or any repeated UI pattern.

The <slot> Element and Shadow DOM

The <slot> element is used inside Shadow DOM to create insertion points where light DOM content appears.

Shadow DOM basics

Shadow DOM encapsulates a component's internal structure. Styles and markup inside a shadow tree do not leak out, and external styles do not leak in.

js
class MyCard extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    shadow.innerHTML = `
      <style>
        .card { border: 1px solid #333; padding: 1rem; }
      </style>
      <div class="card">
        <slot name="title">Default Title</slot>
        <slot>Default content</slot>
      </div>
    `;
  }
}
customElements.define('my-card', MyCard);

Usage:

html
<my-card>
  <h3 slot="title">Custom Title</h3>
  <p>This goes into the default slot.</p>
</my-card>
  • Named slots (<slot name="...">) accept elements with a matching slot attribute.
  • Default slot (unnamed <slot>) catches all unassigned content.
  • Text between <slot> tags is fallback content shown when no content is provided.

Declarative Shadow DOM

Traditionally, Shadow DOM requires JavaScript. Declarative Shadow DOM lets you define shadow trees directly in HTML using a <template> with shadowrootmode:

html
<my-card>
  <template shadowrootmode="open">
    <style>
      .card { border: 1px solid #444; padding: 1rem; }
    </style>
    <div class="card">
      <slot name="title">Default Title</slot>
      <slot>Default content</slot>
    </div>
  </template>
  <h3 slot="title">Server-Rendered Title</h3>
  <p>This works without JavaScript!</p>
</my-card>

This is especially useful for server-side rendering (SSR) — the shadow tree is attached immediately during HTML parsing, with no JavaScript needed for the initial render.

Template Cloning Example

html
<!-- Template: not rendered, just a blueprint -->
<template id="item-template">
  <li class="item">
    <strong class="item-name"></strong>
    <span class="item-desc"></span>
  </li>
</template>

<!-- Container for cloned items -->
<ul id="item-list"></ul>

<script>
  const template = document.getElementById('item-template');
  const list = document.getElementById('item-list');
  const items = [
    { name: 'HTML', desc: 'Structure' },
    { name: 'CSS', desc: 'Style' },
    { name: 'JS', desc: 'Behaviour' },
  ];

  items.forEach(item => {
    const clone = template.content.cloneNode(true);
    clone.querySelector('.item-name').textContent = item.name;
    clone.querySelector('.item-desc').textContent = ' — ' + item.desc;
    list.appendChild(clone);
  });
</script>

What happens to content inside a <template> element when the page loads?

Ready to practice?

Create your free account to access the interactive code editor, run challenges, and track your progress.