Writing templates

lit-html is a templating library that provides fast, efficient rendering and updating of HTML. It lets you express web UI as a function of data.

This section introduces the main features and concepts in lit-html.

Render static HTML

The simplest thing to do in lit-html is to render some static HTML.

import {html, render} from 'lit-html';

// Declare a template
const myTemplate = html`<div>Hello World</div>`;

// Render the template
render(myTemplate, document.body);

The lit-html template is a tagged template literal. The template itself looks like a regular JavaScript string, but enclosed in backticks (`) instead of quotes. The browser passes the string to lit-html’s html tag function.

The html tag function returns a TemplateResult—a lightweight object that represents the template to be rendered.

The render function actually creates DOM nodes and appends them to a DOM tree. In this case, the rendered DOM replaces the contents of the document’s body tag.

Render dynamic text content

You can’t get very far with a static template. lit-html lets you create bindings using ${expression} placeholders in the template literal:

const aTemplate = html`<h1>${title}</h1>`;

To make your template dynamic, you can create a template function. Call the template function any time your data changes.

import {html, render} from 'lit-html';

// Define a template function
const myTemplate = (name) => html`<div>Hello ${name}</div>`;

// Render the template with some data
render(myTemplate('world'), document.body);

// ... Later on ... 
// Render the template with different data
render(myTemplate('lit-html'), document.body);

When you call the template function, lit-html captures the current expression values. The template function doesn’t create any DOM nodes, so it’s fast and cheap.

The template function returns a TemplateResult that’s a function of the input data. This is one of the main principles behind using lit-html: creating UI as a function of state.

When you call render, lit-html only updates the parts of the template that have changed since the last render. This makes lit-html updates very fast.

Using expressions

The previous example shows interpolating a simple text value, but the binding can include any kind of JavaScript expression:

const myTemplate = (subtotal, tax) => html`<div>Total: ${subtotal + tax}</div>`;
const myTemplate2 = (name) => html`<div>${formatName(name.given, name.family, name.title)}</div>`;

Bind to attributes

In addition to using expressions in the text content of a node, you can bind them to a node’s attribute and property values, too.

By default, an expression in the value of an attribute creates an attribute binding:

// set the class attribute
const myTemplate = (data) => html`<div class=${data.cssClass}>Stylish text.</div>`;

Since attribute values are always strings, the expression should return a value that can be converted into a string.

Use the ? prefix for a boolean attribute binding. The attribute is added if the expression evaluates to a truthy value, removed if it evaluates to a falsy value:

const myTemplate2 = (data) => html`<div ?disabled=${!data.active}>Stylish text.</div>`;

Bind to properties

You can also bind to a node’s JavaScript properties using the . prefix and the property name:

const myTemplate3 = (data) => html`<my-list .listItems=${data.items}></my-list>`;

You can use property bindings to pass complex data down the tree to subcomponents.

Note that the property name in this example—listItems—is mixed case. Although HTML attributes are case-insensitive, lit-html preserves the case when it processes the template.

Add event listeners

Templates can also include declarative event listeners. An event listener looks like an attribute binding, but with the prefix @ followed by an event name:

const myTemplate = () => html`<button @click=${clickHandler}>Click Me!</button>`;

This is equivalent to calling addEventListener('click', clickHandler) on the button element.

The event listener can be either a plain function, or an object with a handleEvent method:

const clickHandler = {
  // handleEvent method is required.
  handleEvent(e) { 
    console.log('clicked!');
  },
  // event listener objects can also define zero or more of the event 
  // listener options: capture, passive, and once.
  capture: true,
};

Event listener objects. When you specify a listener using an event listener object, the listener object itself is set as the event context (this value).

Nest and compose templates

You can also compose templates to create more complex templates. When a binding in the text content of a template returns a TemplateResult, the TemplateResult is interpolated in place.

const myHeader = html`<h1>Header</h1>`;
const myPage = html`
  ${myHeader}
  <div>Here's my main page.</div>
`;

You can use any expression that returns a TemplateResult, like another template function:

// some complex view
const myListView = (items) => html`<ul>...</ul>`;

const myPage = (data) => html`
  ${myHeader}
  ${myListView(data.items)}
`;

Composing templates opens a number of possibilities, including conditional and repeating templates.

Conditional templates

lit-html has no built-in control-flow constructs. Instead you use normal JavaScript expressions and statements.

Conditionals with ternary operators

Ternary expressions are a great way to add inline conditionals:

html`
  ${user.isloggedIn
      ? html`Welcome ${user.name}`
      : html`Please log in`
  }
`;

Conditionals with if statements

You can express conditional logic with if statements outside of a template to compute values to use inside of the template:

getUserMessage() {
  if (user.isloggedIn) {
    return html`Welcome ${user.name}`;
  } else {
    return html`Please log in`;
  }
}

html`
  ${getUserMessage()}
`

Repeating templates

You can use standard JavaScript constructs to create repeating templates.

lit-html also provides some special functions, called directives, for use in templates. You can use the repeat directive to build certain kinds of dynamic lists more efficiently.

Repeating templates with Array.map

To render lists, you can use Array.map to transform a list of data into a list of templates:

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

Note that this expression returns an array of TemplateResult objects. lit-html will render an array or iterable of subtemplates and other values.

Repeating templates with looping statements

You can also build an array of templates and pass it into a template binding.

const itemTemplates = [];
for (const i of items) {
  itemTemplates.push(html`<li>${i}</li>`);
}

html`
  <ul>
    ${itemTemplates}
  </ul>
`;

Repeating templates with the repeat directive

In most cases, using loops or Array.map is an efficient way to build repeating templates. However, if you want to reorder a large list, or mutate it by adding and removing individual entries, this approach can involve recreating a large number of DOM nodes.

The repeat directive can help here. Directives are special functions that provide extra control over rendering. lit-html comes with some built-in directives like repeat.

The repeat directive performs efficient updates of lists based on user-supplied keys:

repeat(items, keyFunction, itemTemplate)

Where:

For example:

const employeeList = (employees) => html`
  <ul>
    ${repeat(employees, (employee) => employee.id, (employee, index) => html`
      <li>${index}: ${employee.familyName}, ${employee.givenName}</li>
    `)}
  </ul>
`;

If you re-sort the employees array, the repeat directive reorders the existing DOM nodes.

To compare this to lit-html’s default handling for lists, consider reversing a large list of names:

Which repeat is more efficient depends on your use case: if updating the DOM nodes is more expensive than moving them, use the repeat directive. Otherwise, use Array.map or looping statements.

Rendering nothing

Sometimes, you may want to render nothing at all. The values undefined, null and the empty string ('') in a text binding all render an empty text node. In most cases, that’s exactly what you want:

import {html} from 'lit-html';
${user.isAdmin
      ? html`<button>DELETE</button>`
      : ''
  }

The DOM is inconsistent in some cases with its behavior towards undefined & null. Using an empty string ('') is the most consistent.

nothing and the slot fallback content

nothing is a sentinel value provided by lit-html. It really does render nothing, because it clears the Part.

Why is this important? Imagine you have a shadow DOM enabled custom element, shadow-element, that uses a slot. The template looks like this:

import {html} from 'lit-html';
html`<slot>Sorry, no content available. I am just fallback content</slot>`;

The slot defines fallback content for when there is no content defined to be put in the slot. So, extending on our previous example:

import {nothing, html} from 'lit-html';

html`
<shadow-element>${user.isAdmin
        ? html`<button>DELETE</button>`
        : nothing
      }</shadow-element>
`;

If the user is logged in, DELETE button is rendered. If the user is not logged in, nothing is rendered inside of shadow-element. therefore the slot is empty and its fallback content “Sorry, no content available. I am just fallback content” will be rendered.

Whitespace creates text nodes. For the example to work, the text binding inside <shadow-element> must be the entire contents of <shadow-element>. Any whitespace outside of the binding delimiters adds static text nodes to the template, suppressing the fallback content. However, whitespace inside the binding delimiters is fine.

Caching template results: the cache directive

In most cases, JavaScript conditionals are all you need for conditional templates. However, if you’re switching between large, complicated templates, you might want to save the cost of recreating DOM on each switch.

In this case, you can use the cache directive. Directives are special functions that provide extra control over rendering. The cache directive caches DOM for templates that aren’t being rendered currently.

const detailView = (data) => html`<div>...</div>`; 
const summaryView = (data) => html`<div>...</div>`;

html`${cache(data.showDetails
  ? detailView(data) 
  : summaryView(data)
)}`

When lit-html re-renders a template, it only updates the modified portions: it doesn’t create or remove any more DOM than it needs to. But when you switch from one template to another, lit-html needs to remove the old DOM and render a new DOM tree.

The cache directive caches the generated DOM for a given binding and input template. In the example above, it would cache the DOM for both the summaryView and detailView templates. When you switch from one view to another, lit-html just needs to swap in the cached version of the new view, and update it with the latest data.