Skip to content

Lit Templates

This lesson explains Lit templates and how to use the html tagged template literal to render efficient, declarative UI inside Lit Web Components.

What Are Lit Templates?

Lit templates are HTML markup written with the html tagged template literal. They let you define component UI declaratively, bind dynamic values, and return a TemplateResult from the render() method.

Lit efficiently updates only the parts of the DOM that change, which makes templates a core feature of building fast Lit components.

Concept Description
html`...` Tagged template for markup
render() Method that returns the component UI
TemplateResult Renderable Lit template output
Bindings Dynamic values inside ${ }
Reactivity Lit re-renders when properties change
Main Benefit Declarative UI with efficient DOM updates

Basic Lit Template Example

import { LitElement, html, css } from "lit";
import { customElement, property } from "lit/decorators.js";

@customElement("greeting-card")
class GreetingCard extends LitElement {
  @property()
  name = "Guest";

  render() {
    return html`
      <article class="card">
        <h2>Hello, ${this.name}!</h2>
        <p>Welcome to Lit templates.</p>
      </article>
    `;
  }
}

The render() method returns an html template. When name changes, Lit updates the rendered output efficiently.

How Lit Templates Work

Step What Happens
1. Define template Write markup with html`...`
2. Bind values Insert dynamic data with ${ }
3. Return from render() Lit converts it to a TemplateResult
4. Property changes Lit schedules a re-render
5. DOM update Only changed parts of the template update

Template Bindings

render() {
  return html`
    <section>
      <h2>${this.title}</h2>
      <p>${this.description}</p>
      <button
        type="button"
        ?disabled="${this.loading}"
        @click="${this.handleClick}"
      >
        ${this.buttonLabel}
      </button>
    </section>
  `;
}
Syntax Purpose
${value} Text, attribute, or property binding
?attr="${bool}" Boolean attribute binding
.prop="${value}" Property binding
@event="${handler}" Event listener binding

Conditional Rendering

render() {
  return html`
    <section>
      ${this.loading
        ? html`<p>Loading...</p>`
        : html`<p>${this.message}</p>`
      }
    </section>
  `;
}

Use JavaScript conditionals inside templates to render different markup based on component state.

Rendering Lists

render() {
  return html`
    <ul>
      ${this.items.map(
        (item) => html`
          <li>${item.label}</li>
        `
      )}
    </ul>
  `;
}

Map arrays to nested html templates to render lists of items dynamically.

Combining Templates and Styles

static styles = css`
  :host {
    display: block;
  }

  .card {
    padding: 1rem;
    border: 1px solid #dbe5f1;
    border-radius: 0.75rem;
  }
`;

render() {
  return html`
    <article class="card">
      <slot></slot>
    </article>
  `;
}

Lit components usually combine static styles with render() templates inside Shadow DOM.

Slots in Lit Templates

render() {
  return html`
    <article class="card">
      <header>
        <slot name="title"></slot>
      </header>
      <div class="body">
        <slot></slot>
      </div>
      <footer>
        <slot name="footer"></slot>
      </footer>
    </article>
  `;
}
<lit-card>
  <h2 slot="title">Billing</h2>
  <p>Your plan renews tomorrow.</p>
  <button slot="footer" type="button">Manage</button>
</lit-card>

Lit templates support default and named slots just like native Web Components.

Static Templates for Performance

const loadingTemplate = html`
  <p class="loading">Loading...</p>
`;

const emptyTemplate = html`
  <p class="empty">No results found.</p>
`;

render() {
  if (this.loading) return loadingTemplate;
  if (this.items.length === 0) return emptyTemplate;

  return html`
    <ul>
      ${this.items.map(
        (item) => html`<li>${item}</li>`
      )}
    </ul>
  `;
}

Reuse static templates outside render() when markup does not depend on changing values. This can improve performance.

Nested Templates

renderItem(item) {
  return html`
    <li>
      <strong>${item.name}</strong>
      <span>${item.role}</span>
    </li>
  `;
}

render() {
  return html`
    <ul>
      ${this.users.map((user) => this.renderItem(user))}
    </ul>
  `;
}

Split complex UI into helper methods that return smaller template fragments. This keeps render() easier to read.

Lit Templates vs innerHTML

Feature Lit Templates innerHTML
Updates Efficient partial updates Reparses full HTML string
Bindings Built-in attribute and event bindings Manual string building
Security Escapes content by default Riskier with untrusted input
Readability Declarative component UI String-heavy and harder to maintain

When to Use Lit Templates

  • You are building components with the Lit library.
  • You want declarative UI with efficient updates.
  • You need bindings for text, attributes, and events.
  • You render lists, conditionals, and reusable fragments.
  • You combine Shadow DOM styles with component markup.
  • You want a cleaner alternative to manual innerHTML.

Common Lit Template Use Cases

  • Cards, alerts, badges, and buttons in a design system.
  • Forms with conditional fields and validation messages.
  • Lists, tables, and menus rendered from arrays.
  • Dialogs and drawers with slot-based layouts.
  • Loading, empty, and error states in data components.
  • Reusable template fragments for repeated UI patterns.

Lit Template Best Practices

  • Keep render() focused on markup and composition.
  • Use helper methods for repeated template fragments.
  • Prefer static templates for unchanging markup blocks.
  • Use property bindings for rich JavaScript values.
  • Use boolean and event binding syntax consistently.
  • Avoid unnecessary re-renders by keeping state updates clean.
  • Combine templates with slots for flexible component APIs.

Common Lit Template Mistakes

  • Putting side effects inside render().
  • Building large HTML strings instead of using html`...`.
  • Forgetting to return a template from render().
  • Using innerHTML inside Lit components unnecessarily.
  • Creating new functions inline on every render without need.
  • Not using nested html templates for conditionals and lists.
  • Mixing business logic and rendering in one large method.

Key Takeaways

  • Lit templates use the html tagged template literal.
  • render() returns the component's declarative UI.
  • Bindings handle text, attributes, properties, and events.
  • Conditionals, lists, slots, and static templates are common patterns.
  • Lit templates are efficient and readable for modern Web Components.

Pro Tip

Treat render() as a pure view function: return templates based on state, move event handling and data work to separate methods, and extract repeated markup into helpers.