How Templates and Slots Work

Mikey NicholsMikey Nichols
5 min read

Introduction

When developing for the web today, it is crucial to write modular and maintainable code. Web components offer a powerful set of tools consisting of <template>s and <slot>s, which allow us to define reusable content structures and facilitate dynamic content insertion within a web component. In this article, we explore the functionalities of these elements and demonstrate how they can be leveraged to build highly reusable user interface elements that are native to HTML.


The <template> Element

The <template> element allows us to define HTML fragments that are not rendered immediately when the page loads. Instead, these fragments can be instantiated and inserted into the DOM at runtime using JavaScript. This approach promotes code reuse and helps maintain a clean and efficient DOM structure.

Example:

<template id="my-template">
  <style>
    .message {
      color: blue;
      font-size: 20px;
    }
  </style>
  <div class="message">Hello, Friend!</div>
</template>

In this example, the <template> element contains a styled <div> that can be reused multiple times within the document.

Utilizing the Template in JavaScript:

// Access the template element
const template = document.getElementById('my-template');

// Clone the content of the template
const clone = document.importNode(template.content, true);

// Append the cloned content to the body
document.body.appendChild(clone);

Here, the template's content is cloned and appended to the document's body, rendering the defined message.

If we want to load a web component without embedding the HTML template directly in the file, we can dynamically fetch the HTML template from an external source (such as a separate HTML file, a server endpoint, or a CDN). This approach is useful for:

  1. Modularity: Keep our HTML templates separate from our JavaScript logic.

  2. Lazy Loading: Load templates only when needed, improving initial page load performance.

  3. Reusability: Share templates across multiple components or projects.

Fetch the Template from an External HTML File

We can store our HTML template in a separate file (e.g., template.html) and fetch it using JavaScript.

Example:

  • template.html (external file):

      <template id="my-template">
        <div class="card">
          <h2><slot name="title">Default Title</slot></h2>
          <p><slot name="content">Default content goes here.</slot></p>
        </div>
      </template>
    
  • JavaScript for Web Component:

      class MyComponent extends HTMLElement {
        constructor() {
          super();
          this.attachShadow({ mode: 'open' });
        }
    
        async connectedCallback() {
          // Fetch the external template (replace 'path/to/' with the actual path)
          const response = await fetch('path/to/template.html');
          const text = await response.text();
    
          // Parse the fetched HTML and extract the template
          const parser = new DOMParser();
          const doc = parser.parseFromString(text, 'text/html');
          const template = doc.getElementById('my-template').content;
    
          // Clone and append the template to the shadow DOM
          this.shadowRoot.appendChild(template.cloneNode(true));
        }
      }
    
      customElements.define('my-component', MyComponent);
    

Avoid Deprecated <link rel="import">

HTML imports (<link rel="import">) were once used to load external HTML files, but this feature is deprecated and unsupported in modern browsers. While we may encounter it in legacy code, avoid using this approach.

Use JavaScript Modules and Template Literals

If we prefer not to fetch templates from external files, we can also define them as strings in JavaScript modules.

Example:

  • JavaScript Module:

      const templateString = `
        <template id="my-template">
          <div class="card">
            <h2><slot name="title">Default Title</slot></h2>
            <p><slot name="content">Default content goes here.</slot></p>
          </div>
        </template>
      `;
    
      export function getTemplate() {
        const parser = new DOMParser();
        const doc = parser.parseFromString(templateString, 'text/html');
        return doc.getElementById('my-template').content;
      }
    
  • Web Component:

      import { getTemplate } from './template.js';
    
      class MyComponent extends HTMLElement {
        constructor() {
          super();
          const shadowRoot = this.attachShadow({ mode: 'open' });
          const template = getTemplate();
          shadowRoot.appendChild(template.cloneNode(true));
        }
      }
    
      customElements.define('my-component', MyComponent);
    

The <slot> Element

The <slot> element serves as a placeholder within a web component's shadow DOM, allowing us to define where external content (light DOM) should be inserted. This mechanism enables the creation of flexible and customizable components that can accept and display varying content provided by the component's user.

Example:

<!-- Define the custom element -->
<template id="my-component-template">
  <style>
    .container {
      border: 1px solid #ccc;
      padding: 10px;
    }
  </style>
  <div class="container">
    <slot name="header"></slot>
    <p>This is the main content of the component.</p>
    <slot name="footer"></slot>
  </div>
</template>

<script>
  class MyComponent extends HTMLElement {
    constructor() {
      super();
      const template = document.getElementById('my-component-template').content;
      const shadowRoot = this.attachShadow({ mode: 'open' });
      shadowRoot.appendChild(template.cloneNode(true));
    }
  }

  customElements.define('my-component', MyComponent);
</script>

<!-- Use the custom element with slotted content -->
<my-component>
  <h2 slot="header">Welcome!</h2>
  <p slot="footer">Thanks for visiting.</p>
</my-component>

In this example, the <my-component> element defines slots named "header" and "footer." When the component is used, content is assigned to these slots using the slot attribute, enabling dynamic insertion of external content into the component's structure.


Combining <template> and <slot> for Dynamic Components

By integrating <template> and <slot>, we can create dynamic and reusable components that maintain a clear separation between structure and content.

Example:

<template id="card-template">
  <style>
    .card {
      border: 1px solid #ddd;
      border-radius: 5px;
      padding: 15px;
      box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1);
    }
  </style>
  <div class="card">
    <slot name="title"></slot>
    <slot name="content"></slot>
  </div>
</template>

<script>
  class CardComponent extends HTMLElement {
    constructor() {
      super();
      const template = document.getElementById('card-template').content;
      const shadowRoot = this.attachShadow({ mode: 'open' });
      shadowRoot.appendChild(template.cloneNode(true));
    }
  }

  customElements.define('card-component', CardComponent);
</script>

<!-- Using the card component -->
<card-component>
  <h3 slot="title">Card Title</h3>
  <p slot="content">This is the content of the card.</p>
</card-component>

Here, the CardComponent uses a template to define its structure and styling. The slots "title" and "content" allow users to insert custom content, making the component reusable and adaptable.


Conclusion

The <template> and <slot> elements are fundamental tools for building modular and dynamic web components. The <template> element enables the definition of reusable HTML fragments, while the <slot> element facilitates the insertion of external content into predefined placeholders. By leveraging these elements, developers can create flexible, maintainable, and encapsulated components that enhance the structure and functionality of web applications.

0
Subscribe to my newsletter

Read articles from Mikey Nichols directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Mikey Nichols
Mikey Nichols

I am an aspiring web developer on a mission to kick down the door into tech. Join me as I take the essential steps toward this goal and hopefully inspire others to do the same!