Templates
This documentation is a work in progress. It describes prerelease software, and is subject to change.
- Define and render a template
- Compose a template from other templates
- Specify the render root
- Template syntax cheat sheet
Define and render a template
To define a template for a LitElement component, write a render
function for your element class:
class MyElement extends LitElement {
render() {
return html`<p>template content</p>`;
}
}
-
Write your template in HTML inside a JavaScript template literal by enclosing the raw HTML in back-ticks (
``
). -
Tag your template literal with the
html
helper function, so thatrender
returns a lit-htmlTemplateResult
.
Example
import { LitElement, html } from '@polymer/lit-element';
class MyElement extends LitElement {
// Implement `render` to define a template for your element.
render(){
/**
* Return a lit-html `TemplateResult`.
*
* To create a `TemplateResult`, tag a JavaScript template literal
* with the `html` helper function.
*/
return html`
<div>
<p>A paragraph</p>
</div>
`;
}
}
customElements.define('my-element', MyElement);
Design a performant template
LitElement renders and re-renders asynchronously, updating in response to batched property changes (see Element update lifecycle for more information).
During an update, only the parts of the DOM that change are re-rendered. To get the performance benefits of this model, you should design your element’s template as a pure function of its properties.
To do this, make sure the render
function:
- Does not change the element’s state.
- Does not have any side effects.
- Only depends on the element’s properties.
- Returns the same result when given the same property values.
Also, avoid making DOM updates outside of render
. Instead, express the element’s template as a function of its state, and capture its state in properties.
The following code uses inefficient DOM manipulation:
dom-manip.js
// Anti-pattern. Avoid!
constructor() {
super();
this.addEventListener('stuff-loaded', (e) => {
this.shadowRoot.getElementById('message').innerHTML=e.detail;
});
this.loadStuff();
}
render() {
return html`
<p id="message">Loading</p>
`;
}
We can improve the template by capturing the load message as a property, and setting the property in response to the event:
update-properties.js
constructor() {
super();
this.message = 'Loading';
this.addEventListener('stuff-loaded', (e) => { this.message = e.detail } );
this.loadStuff();
}
render() {
return html`
<p>${this.message}</p>
`;
}
Use properties, loops, and conditionals in a template
Properties
To add a property value to a template, insert it with ${this.propName}
:
static get properties() {
return { myProp: String };
}
...
render() {
return html`<p>${this.myProp}</p>`;
}
Loops
Iterate over an array:
html`<ul>
${this.myArray.map(i => html`<li>${i}</li>`)}
</ul>`;
Conditionals
Render based on a Boolean condition:
html`
${this.myBool?
html`<p>Render some HTML if myBool is true</p>`:
html`<p>Render some other HTML if myBool is false</p>`}
`;
Examples
import { LitElement, html } from '@polymer/lit-element';
class MyElement extends LitElement {
static get properties() {
return {
myString: { type: String },
myArray: { type: Array },
myBool: { type: Boolean }
};
}
constructor() {
super();
this.myString = 'Hello World';
this.myArray = ['an','array','of','test','data'];
this.myBool = true;
}
render() {
return html`
<p>${this.myString}</p>
<ul>
${this.myArray.map(i => html`<li>${i}</li>`)}
</ul>
${this.myBool?
html`<p>Render some HTML if myBool is true</p>`:
html`<p>Render some other HTML if myBool is false</p>`}
`;
}
}
customElements.define('my-element', MyElement);
Bind properties to child elements
You can insert JavaScript expressions as placeholders for HTML text content, attributes, Boolean attributes, properties, and event handlers.
- Text content:
<p>${...}</p>
- Attribute:
<p id="${...}"></p>
- Boolean attribute:
?checked="${...}"
- Property:
.value="${...}"
- Event handler:
@event="${...}"
JavaScript expressions can include your element’s properties. LitElement observes and reacts to property changes, so your templates update automatically.
Data bindings are always one-way (parent to child). To share data from a child element to its parent, fire an event and capture the relevant data in the detail
property.
Bind to text content
Bind prop1
to text content:
html`<div>${this.prop1}</div>`
Bind to an attribute
Bind prop2
to an attribute:
html`<div id="${this.prop2}"></div>`
Attribute values are always strings, so an attribute binding should return a value that can be converted into a string.
Bind to a boolean attribute
Bind prop3
to a boolean attribute:
html`<input type="checkbox" ?checked="${this.prop3}">i like pie</input>`
Boolean attributes are added if the expression evaluates to a truthy value, and removed if it evaluates to a falsy value.
Bind to a property
Bind prop4
to a property:
html`<input type="checkbox" .value="${this.prop4}"/>`
Bind to an event handler
Bind clickHandler
to a click
event:
html`<button @click="${this.clickHandler}">pie?</button>`
The default event context for @event
expressions is this
, so there is no need to bind the handler function.
Examples
my-element.js
import { LitElement, html } from '@polymer/lit-element';
class MyElement extends LitElement {
static get properties() {
return {
prop1: String,
prop2: String,
prop3: Boolean,
prop4: String
};
}
constructor() {
super();
this.prop1 = 'text binding';
this.prop2 = 'mydiv';
this.prop3 = true;
this.prop4 = 'pie';
}
render() {
return html`
<!-- text binding -->
<div>${this.prop1}</div>
<!-- attribute binding -->
<div id="${this.prop2}">attribute binding</div>
<!-- boolean attribute binding -->
<div>
boolean attribute binding
<input type="checkbox" ?checked="${this.prop3}"/>
</div>
<!-- property binding -->
<div>
property binding
<input type="checkbox" .value="${this.prop4}"/>
</div>
<!-- event handler binding -->
<div>event handler binding
<button @click="${this.clickHandler}">click</button>
</div>
`;
}
clickHandler(e) {
console.log(e.target);
}
}
customElements.define('my-element', MyElement);
Render light DOM children with the slot element
Shadow DOM vs light DOM
Since the introduction of shadow DOM, we use the term “light DOM” to refer to nodes that appear in the main DOM tree.
By default, if a custom element has light DOM children in HTML, they do not render at all:
<my-element>
<p>I won't render</p>
</my-element>
You can make them render using the <slot>
element.
Use the slot
element
To render an element’s light DOM children, create a <slot>
for them in the element’s template. For example:
render(){
return html`
<div>
<slot></slot>
</div>
`;
}
Light DOM children will now render in the <slot>
:
<my-element>
<p>Render me</p>
</my-element>
Arbitrarily many light DOM children can populate a single slot:
<my-element>
<p>Render me</p>
<p>Me too</p>
<p>Me three</p>
</my-element>
Use named slots
To assign a light DOM child to a specific slot, ensure that the child’s slot
attribute matches the slot’s name
attribute:
render(){
return html`
<div>
<slot name="one"></slot>
</div>
`;
}
index.html
<my-element>
<p slot="one">Include me in slot "one".</p>
</my-element>
-
Named slots only accept light DOM children with a matching
slot
attribute.For example,
<slot name="one"></slot>
only accepts children with the attributeslot="one"
. -
Light DOM children with a
slot
attribute will only be placed in a slot with a matchingname
attribute.For example,
<p slot="one">...</p>
will only be placed in<slot name="one"></slot>
.
Examples
my-element.js
<stack-blitz
folder="/includes/projects/projects/docs/templates/namedslots/my-element.js"
openFile="">
</stack-blitz>
index.html
<stack-blitz
folder="/includes/projects/projects/docs/templates/namedslots/index.html"
openFile="">
</stack-blitz>
Use name
, not id
, to select slots.
Note that a slot
’s id
attribute has no effect!
my-element.js
render(){
return html`
<div>
<slot id="one"></slot>
</div>
`;
}
index.html
<my-element>
<p slot="one">nope.</p>
<p>ohai..</p>
</my-element>
Compose a template from other templates
You can compose LitElement templates from other LitElement templates. In the following example, we compose a template for an element called <my-page>
from smaller templates for the standard HTML elements <header>
, <article>
, and <footer>
:
class MyPage extends LitElement {
render() {
return html`
${this.headerTemplate}
${this.articleTemplate}
${this.footerTemplate}
`;
}
static get headerTemplate() {
return html`<header>header</header>`;
}
static get articleTemplate() {
return html`<article>article</article>`;
}
static get footerTemplate() {
return html`<footer>footer</footer>`;
}
}
You can also compose templates by importing other elements and using them in your template:
import './my-header.js';
import './my-article.js';
import './my-footer.js';
class MyPage extends LitElement {
render() {
return html`
<my-header></my-header>
<my-article></my-article>
<my-footer></my-footer>
`;
}
}
Specify the render root
The node into which your component’s template will render is called its render root.
By default, LitElement creates an open shadowRoot
and renders inside it, producing the following DOM structure:
<my-element>
#shadow-root
<p>child 1</p>
<p>child 2</p>
To customize a component’s render root, implement createRenderRoot
and return the node you want the template to render into.
For example, to render the template into the main DOM tree as your element’s light DOM:
<my-element>
<p>child 1</p>
<p>child 2</p>
Implement createRenderRoot
and return this
:
class LightDom extends LitElement {
render() {
return html`
<p>This template renders in light DOM.</p>
`;
}
createRenderRoot() {
/**
* Render template in light DOM. Note that shadow DOM features like
* encapsulated CSS are unavailable.
*/
return this;
}
}
Template syntax cheat sheet
Render
render() { return html`<p>template</p>`; }
Properties, loops, conditionals
// Property
html`<p>${this.myProp}</p>`;
// Loop
html`${this.myArray.map(i => html`<li>${i}</li>`;)}`;
// Conditional
html`${this.myBool?html`<p>foo</p>`:html`<p>bar</p>`}`;
Data bindings
// Attribute
html`<p id="${...}">`;
// Boolean attribute
html`<input type="checkbox" ?checked="${...}">`;
// Property
html`<input .value="${...}">`;
// Event handler
html`<button @click="${this.doStuff}"></button>`;
Composition
// From multiple templates on same class
render() {
return html`
${this.headerTemplate}
<article>article</article>
`;
}
static get headerTemplate() {
return html`<header>header</header>`;
}
// By importing elements
import './my-header.js';
class MyPage extends LitElement{
render() {
return html`
<my-header></my-header>
<article>article</article>
`;
}
}
Slots
render() { return html`<slot name="thing"></slot>`; }
<my-element>
<p slot="thing">stuff</p>
</my-element>