Skip to content

Polymer HTML Templates — Reusable Markup with the Template Element

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about Polymer HTML Templates. We cover key concepts, practical examples, and best practices to help you master this topic.

HTML Templates provide reusable markup fragments that are parsed once and cloned for each use — they are not rendered until activated, making them efficient for component rendering.

What You'll Learn

  • The HTML template element
  • Cloning and stamping templates
  • Conditional and repeat templates
  • lit-html template literals
  • Template composition

Why It Matters

Templates separate markup from logic. The browser parses the template once, and LitElement's template literals provide reactive bindings that only update changed parts.

Real-World Use

A dashboard with 50+ data cards using the same template — parsed once, cloned per card, data-bound through LitElement's reactive properties.

Template Architecture

flowchart TD
    A[Templates] --> B[HTML Template]
    A --> C[Template Literals]
    B --> D[Parsed Once]
    B --> E[Cloned Per Use]
    C --> F[Expressions]
    C --> G[Reactive Updates]
    C --> H[Directives]
    style A fill:#e6f3ff,stroke:#4a90d9,stroke-width:2px

Basic HTML Template

<template id="user-template">
  <style>
    .card { display: flex; gap: 12px; padding: 12px; border: 1px solid #e0e0e0; border-radius: 8px; }
    .avatar { width: 48px; height: 48px; border-radius: 50%; background: #1a237e; color: white; display: flex; align-items: center; justify-content: center; font-weight: bold; }
    .name { font-weight: bold; }
    .email { color: #666; font-size: 14px; }
  </style>
  <div class="card">
    <div class="avatar" id="avatar"></div>
    <div class="info">
      <div class="name" id="name"></div>
      <div class="email" id="email"></div>
    </div>
  </div>
</template>

<script>
class UserList extends HTMLElement {
  constructor() { super(); this.attachShadow({ mode: 'open' }); }
  connectedCallback() {
    const users = [
      { name: 'Alice', email: 'alice@example.com' },
      { name: 'Bob', email: 'bob@example.com' }
    ];
    const template = document.getElementById('user-template');
    const fragment = document.createDocumentFragment();
    users.forEach(user => {
      const clone = template.content.cloneNode(true);
      clone.getElementById('avatar').textContent = user.name.charAt(0);
      clone.getElementById('name').textContent = user.name;
      clone.getElementById('email').textContent = user.email;
      fragment.appendChild(clone);
    });
    this.shadowRoot.appendChild(fragment);
  }
}
customElements.define('user-list', UserList);
</script>

Expected output: The template is parsed once. JavaScript clones and populates it for each user.

Conditional Templates

class ConditionalRender extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.isLoggedIn = false;
    this.user = null;
  }

  connectedCallback() {
    this._render();
    setTimeout(() => { this.isLoggedIn = true; this.user = { name: 'Alice' }; this._render(); }, 2000);
  }

  _render() {
    if (!this.isLoggedIn) {
      this.shadowRoot.innerHTML = `
        <div class="login-prompt"><p>Please log in</p><button id="btn">Log In</button></div>`;
      this.shadowRoot.querySelector('#btn').onclick = () => {
        this.isLoggedIn = true; this.user = { name: 'Alice' }; this._render();
      };
    } else {
      this.shadowRoot.innerHTML = `
        <div class="welcome"><h3>Welcome, ${this.user.name}</h3></div>`;
    }
  }
}
customElements.define('conditional-render', ConditionalRender);

Expected output: Shows a login prompt initially, then switches to the welcome view after login.

Repeat Templates (lit-html)

import { LitElement, html } from 'lit';

class RepeatList extends LitElement {
  static properties = { items: { type: Array }, filter: { type: String } };
  constructor() {
    super();
    this.items = [
      { id: 1, name: 'Task 1', priority: 'high' },
      { id: 2, name: 'Task 2', priority: 'medium' },
      { id: 3, name: 'Task 3', priority: 'low' }
    ];
    this.filter = 'all';
  }

  get _filtered() {
    if (this.filter === 'all') return this.items;
    return this.items.filter(i => i.priority === this.filter);
  }

  render() {
    return html`
      <div>
        ${['all', 'high', 'medium', 'low'].map(p => html`
          <button @click=${() => this.filter = p}
                  style="${this.filter === p ? 'font-weight:bold' : ''}">${p}</button>
        `)}
      </div>
      <div>${this._filtered.map(item => html`
        <div>${item.name} <span>${item.priority}</span></div>
      `)}</div>
    `;
  }
}
customElements.define('repeat-list', RepeatList);

Expected output: A filterable task list using lit-html's Array.map. Clicking a filter re-renders only matching items.

Template Composition

import { LitElement, html } from 'lit';

class BaseLayout extends LitElement {
  static properties = { title: { type: String } };
  render() {
    return html`
      <header style="background:#1a237e;color:white;padding:16px">
        <slot name="title">${this.title || 'Title'}</slot>
      </header>
      <main style="padding:16px"><slot></slot></main>
      <footer style="background:#f5f5f5;padding:8px 16px;font-size:14px">
        <slot name="footer">Footer</slot>
      </footer>
    `;
  }
}
customElements.define('base-layout', BaseLayout);

class DashboardPage extends LitElement {
  render() {
    return html`
      <base-layout>
        <span slot="title">Dashboard</span>
        <p>Main content here</p>
        <span slot="footer">v1.0</span>
      </base-layout>
    `;
  }
}
customElements.define('dashboard-page', DashboardPage);

Expected output: The dashboard composes with the layout using slots.

Common Mistakes

  1. Using innerHTML instead of template cloning - Templates parse once; innerHTML re-parses every time.

  2. Modifying template content directly - Clone (content.cloneNode(true)) before modifying.

  3. Using string concatenation in lit-html - Use html... template literals.

  4. Not keying repeat lists - Use repeat directive with key function for efficient updates.

Practice Questions

  1. How does the HTML template element differ from innerHTML?
  2. How do you clone a template for each data item?
  3. How do you conditionally render different templates?
  4. How does lit-html handle repeat rendering?
  5. What is the purpose of the repeat directive key function?

Challenge: Build a product catalog with template per product, conditional out-of-stock rendering, repeat with keys, filter/sort controls, and cart summary composed from selected items.

FAQ

Can I use HTML templates with LitElement?

LitElement uses tagged template literals (html...), not