Template syntax reference

This documentation is a work in progress. It describes prerelease software, and is subject to change.

lit-html templates are written using JavaScript template literals tagged with the html tag. The contents of the literal are mostly plain, declarative, HTML:

html`<h1>Hello World</h1>`

Bindings or expressions are denoted with the standard JavaScript syntax for template literals:

html`<h1>Hello ${name}</h1>`

Template Structure

lit-html templates must be well-formed HTML, and bindings can only occur in certain places. The templates are parsed by the browser’s built-in HTML parser before any values are interpolated.

No warnings. Most cases of malformed templates are not detectable by lit-html, so you won’t see any warnings—just templates that don’t behave as you expect—so take extra care to structure templates properly.

Follow these rules for well-formed templates:

Binding Types

Expressions can occur in text content or in attribute value positions.

There are a few types of bindings:

Event Listeners

Event listeners can be functions or objects with a handleEvent method. Listeners are passed as both the listener and options arguments to addEventListener/removeEventListener, so that the listener can carry event listener options like capture, passive, and once.

const listener = {
  handleEvent(e) {
    console.log('clicked');
  }
  capture: true;
};

html`<button @click=${listener}>Click Me</button>`

Supported Data Types

Each binding type supports different types of values:

Supported data types for text bindings

Text content bindings accept a large range of value types:

Primitive Values: String, Number, Boolean, null, undefined

Primitives values are converted to strings when interpolated into text content or attribute values. They are checked for equality to the previous value so that the DOM is not updated if the value hasn’t changed.

TemplateResult

Templates can be nested by passing a TemplateResult as a value of an expression:

const header = html`<h1>Header</h1>`;

const page = html`
  ${header}
  <p>This is some text</p>
`;

Node

Any DOM Node can be passed to a text position expression. The node is attached to the DOM tree at that point, and so removed from any current parent:

const div = document.createElement('div');
const page = html`
  ${div}
  <p>This is some text</p>
`;

Arrays / Iterables

Arrays and Iterables of supported types are supported as well. They can be mixed values of different supported types.

const items = [1, 2, 3];
const list = () => html`items = ${items.map((i) => `item: ${i}`)}`;
const items = {
  a: 1,
  b: 23,
  c: 456,
};
const list = () => html`items = ${Object.entries(items)}`;

Control Flow with JavaScript

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

Ifs with ternary operators

Ternary expressions are a great way to add inline-conditionals:

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

Ifs 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()}
`

Loops with Array.map

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

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

Looping statements

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

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

Built-in directives

Directives are functions that can extend lit-html by customizing the way a binding renders.

lit-html includes a few built-in directives.

Directives may change. The exact list of directives included with lit-html, and the API of the directives may be subject to change before lit-html 1.0 is released.

repeat

repeat(items, keyfn, template)

Repeats a series of values (usually TemplateResults) generated from an iterable, and updates those items efficiently when the iterable changes. When the keyFn is provided, key-to-DOM association is maintained between updates by moving DOM when required, and is generally the most efficient way to use repeat since it performs minimum unnecessary work for insertions amd removals.

Example:

import { repeat } from 'lit-html/directives/repeat';

const myTemplate = () => html`
  <ul>
    ${repeat(items, (i) => i.id, (i, index) => html`
      <li>${index}: ${i.name}</li>`)}
  </ul>
`;

If no keyFn is provided, repeat will perform similar to a simple map of items to values, and DOM will be reused against potentially different items.

ifDefined

ifDefined(value)

For AttributeParts, sets the attribute if the value is defined and removes the attribute if the value is undefined.

For other part types, this directive is a no-op.

Example:

import { ifDefined } from 'lit-html/directives/if-defined';

const myTemplate = () => html`
  <div class=${ifDefined(className)}></div>
`;

guard

guard(expressions, valueFn)

Avoids re-evaluating an expensive template function (valueFn) unless one of the identified expressions changes identity. Returns the value of valueFn, which may be cached.

The expressions argument can either be a single (non-array) expression, or an array of multiple expressions to monitor.

The guard directive caches the last-known value of valueFn, and only re-evaluates valueFn if the identity of any of the expressions changes (for example when a primitive changes value or when an object reference changes).

Example:

import { guard } from 'lit-html/directives/guard';

const template = html`
  <div>
    ${guard([items], () => items.map(item => html`${item}`))}
  </div>
`

In this case, the items array is mapped over only when the array reference changes.

until

until(...values)

Renders one of a series of values, including Promises, to a Part.

Values are rendered in priority order, with the first argument having the highest priority and the last argument having the lowest priority. If a value is a Promise, low-priority values will be rendered until it resolves.

The priority of values can be used to create placeholder content for async data. For example, a Promise with pending content can be the first, highest-priority, argument, and a non_promise loading indicator template can be used as the second, lower-priority, argument. The loading indicator will render immediately, and the primary content will render when the Promise resolves.

Example:

import { until } from 'lit-html/directives/until.js';

const content = fetch('./content.txt').then(r => r.text());

html`${until(content, html`<span>Loading...</span>`)}`

asyncAppend and asyncReplace

asyncAppend(asyncIterable)
asyncReplace(asyncIterable)

JavaScript asynchronous iterators provide a generic interface for asynchronous sequential access to data. Much like an iterator, a consumer requests the next data item with a a call to next(), but with asynchronous iterators next() returns a Promise, allowing the iterator to provide the item when it’s ready.

lit-html offers two directives to consume asynchronous iterators:

appending each new value after the previous.

replacing the previous value with the new value.

Example:

const wait = (t) => new Promise((resolve) => setTimeout(resolve, t));
/**
 * Returns an async iterable that yields increasing integers.
 */
async function* countUp() {
  let i = 0;
  while (true) {
    yield i++;
    await wait(1000);
  }
}

render(html`
  Count: <span>${asyncReplace(countUp())}</span>.
`, document.body);

In the near future, ReadableStreams will be async iterables, enabling streaming fetch() directly into a template:

// Endpoint that returns a billion digits of PI, streamed.
const url =
    'https://cors-anywhere.herokuapp.com/http://stuff.mit.edu/afs/sipb/contrib/pi/pi-billion.txt';

const streamingResponse = (async () => {
  const response = await fetch(url);
  return response.body.getReader();
})();
render(html`π is: ${asyncAppend(streamingResponse)}`, document.body);