How to Use HTML Templates and Slots with JavaScript

HTML templates and slots are built-in web standards designed to create reusable, flexible UI components without rendering overhead. The <template> element holds inert HTML markup that the browser ignores until instantiated with JavaScript, while the <slot> element acts as a placeholder inside a Shadow DOM to inject custom user-provided markup. This guide explains what templates and slots are, how they work together within the Document Object Model (DOM), and how to manipulate them using vanilla JavaScript.


What is the <template> Element?

The <template> tag is an HTML mechanism for holding client-side content that is not rendered when the page loads. The content inside a <template> is stored as a DocumentFragment. Because it is inert:

Instantiating a Template with JavaScript

To use the markup inside a <template>, access its .content property, clone the DocumentFragment, and append it to the active DOM.

<template id="user-card-template">
  <div class="user-card">
    <h3 class="user-name"></h3>
    <p class="user-role"></p>
  </div>
</template>

<div id="container"></div>
// 1. Reference the template element
const template = document.getElementById('user-card-template');

// 2. Clone the template's content (true enables a deep clone)
const clone = template.content.cloneNode(true);

// 3. Manipulate the cloned elements before rendering
clone.querySelector('.user-name').textContent = 'Jane Doe';
clone.querySelector('.user-role').textContent = 'Software Engineer';

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

What is the <slot> Element?

The <slot> tag acts as a dynamic placeholder inside a Web Component’s Shadow DOM. It enables content projection (or “slotting”), which allows external HTML markup provided by the consumer to be rendered inside designated locations within the component’s internal structure.

Types of Slots:

  1. Default (Unnamed) Slot: Catches any light DOM elements that do not have a designated slot attribute.
  2. Named Slot: Specified using the name attribute (<slot name="header"></slot>). Content is targeted to this slot using the matching slot="header" attribute in the light DOM.

Combining Templates, Shadow DOM, and Slots

Slots require a Shadow Root to project content properly. Here is how templates and slots operate inside a custom Web Component:

<!-- Component Template with Slots -->
<template id="custom-card-template">
  <style>
    .card { border: 1px solid #ddd; padding: 16px; border-radius: 8px; }
    ::slotted(h2) { margin-top: 0; color: #333; }
  </style>
  <div class="card">
    <!-- Named slot for the header -->
    <slot name="card-title">Default Title</slot>
    
    <!-- Default slot for the body -->
    <slot>Default body text goes here.</slot>
  </div>
</template>

<!-- Custom Element Usage in the Document -->
<custom-card>
  <h2 slot="card-title">Project Alpha</h2>
  <p>This paragraph fills the default, unnamed slot.</p>
</custom-card>
class CustomCard extends HTMLElement {
  constructor() {
    super();

    // 1. Attach Shadow DOM
    const shadowRoot = this.attachShadow({ mode: 'open' });

    // 2. Retrieve and clone the template
    const template = document.getElementById('custom-card-template');
    const content = template.content.cloneNode(true);

    // 3. Append cloned template containing slots to the Shadow Root
    shadowRoot.appendChild(content);
  }
}

// Register the custom element
customElements.define('custom-card', CustomCard);

Manipulating Slots with JavaScript

JavaScript provides built-in methods and events to inspect and interact with slotted content dynamically.

1. Inspecting Slotted Content (assignedNodes and assignedElements)

You can query which nodes or elements are assigned to a slot from inside the Shadow Root:

const slotElement = shadowRoot.querySelector('slot[name="card-title"]');

// Returns all nodes (including text and whitespace)
const nodes = slotElement.assignedNodes();

// Returns only element nodes (e.g., HTML tags)
const elements = slotElement.assignedElements();

console.log(elements[0]); // Outputs: <h2 slot="card-title">Project Alpha</h2>

2. Accessing the Assigned Slot from Light DOM

If you have a reference to an element in the light DOM, you can find the <slot> element it is mapped to using the assignedSlot property:

const heading = document.querySelector('h2[slot="card-title"]');
console.log(heading.assignedSlot); // Returns the corresponding HTMLSlotElement

3. Listening for Slot Changes (slotchange)

The slotchange event fires on a <slot> element whenever its assigned nodes are added, removed, or replaced:

const defaultSlot = shadowRoot.querySelector('slot:not([name])');

defaultSlot.addEventListener('slotchange', (event) => {
  const updatedNodes = defaultSlot.assignedElements();
  console.log('Slot content updated:', updatedNodes);
});