packages/lit-dev-content/site/articles/article/lit-cheat-sheet.md
Do you need a quick reference for the basics of Lit? Look no further! This cheat sheet will help you get started with, or just remember, the features of Lit.
If you are coming from another framework, you might also want to supplement this article with Component Party which compares basic concepts across different frameworks. Just make sure that Lit is selected at the top of the page!
{% aside "positive" "no-header" %}
Use the Table of Contents to jump to a specific section!
{% endaside %}
LitElement is the base class for all Lit components.
<ts-js><span slot="ts"><code>@customElement</code></span><span slot="js"><code>customElements.define</code></span></ts-js> is where you associate the name of your component with the class definition / logic of that component.
render() is the method where you define your component's template using a tagged template literal with html.
Write your HTML in the html tagged template literal.
{% playground-ide "articles/lit-cheat-sheet/define", true %}
{% aside "info" %}
Important rules:
Components are global HTML elements, you currently can't have more than one with the same name on a page.
Components must have dashes in their name (defined using <ts-js><span slot="ts"><code>@customElement</code></span><span slot="js"><code>customElements.define</code></span></ts-js>).
{% endaside %}
Related Documentation & Topics:
@customElementcustomElements.defineTo use a component, import the file with its definition.
{% playground-ide "articles/lit-cheat-sheet/import", true %}
Related Documentation & Topics:
import (external)import() (external)@customElementcustomElements.define (external)Use the html tagged template literal to define your component's template.
{% playground-ide "articles/lit-cheat-sheet/define", true %}
Related Documentation & Topics:
Use standard JavaScript conditional expressions in your template to conditionally render content.
{% playground-ide "articles/lit-cheat-sheet/conditionals", true %}
Related Documentation & Topics:
Lit-html has three types of built-in expressions to set attributes or properties on elements:
.prop=${value}attr=${value}?attr=${value}{% playground-ide "articles/lit-cheat-sheet/expressions", true %}
Related Documentation & Topics:
Lit-html has a built-in event listener expression to add event listeners to elements. You can also use event listeners to mimic two-way databinding with input elements.
{% playground-ide "articles/lit-cheat-sheet/event-listeners", true %}
Related Documentation & Topics:
Lit-html can render JavaScript arrays and iterables. For most simple use cases,
you can use the Array.map() method to render arrays of items or the map()
directive to render any other iterables. This pattern is best used for short,
simple lists.
{% playground-ide "articles/lit-cheat-sheet/render-lists", true %}
Related Documentation & Topics:
map()range()join()For long lists that may change frequently, use the repeat() directive to efficiently re-render only the items that have changed.
{% playground-ide "articles/lit-cheat-sheet/repeat", true %}
For lists that are so long that it would be impractical to render all items at once, use the Lit Virtualizer to render only the items that are currently in view.
{% playground-ide "articles/lit-cheat-sheet/virtualizer", true %}
{% aside "labs" %}
Lit Virtualizer is in labs
meaning that its implementation might change until it has graduated and become stable. Additionally there are many more features to virtualizer, so it is recommended to look at the documentation.
{% endaside %}
Related Documentation & Topics:
To render a string of HTML as HTML in Lit, use the unsafeHTML directive.
{% playground-ide "articles/lit-cheat-sheet/unsafe-html", true %}
{% aside "negative" %}
Be careful when using unsafeHTML as it can open your application up to
cross-site scripting (XSS) and other attacks.
Only use unsafeHTML with trusted sources and strings as you would use
Element.prototype.innerHTML.
{% endaside %}
Related Documentation & Topics:
In rare cases, you need to bind to an HTML tag name to change the rendered
element. You can do this safely with the static-html module and the literal
template tag.
{% playground-ide "articles/lit-cheat-sheet/bind-tag-name", true %}
{% aside "warn" %}
Be careful when using static HTML templates for switching tag names as it requires reapplying all bindings in the template every time the tag name changes.
This can be costly, and in most cases it is recommended to use conditional template rendering instead of switching tag names with static HTML templates.
{% endaside %}
Related Documentation & Topics:
In even rarer cases, you need to bind any arbitrary string value to an HTML tag
name to change the rendered element. You can do this with the unsafeStatic()
directive. This may be helpful if you are implementing an SSR framework that
uses lit-html for rendering.
{% playground-ide "articles/lit-cheat-sheet/unsafe-static", true %}
{% aside "negative" %}
Be careful when using unsafeStatic as it can open your application up to
cross-site scripting (XSS) and other attacks.
Only use unsafeStatic with trusted sources and strings as you would use
Element.prototype.innerHTML. Additionally, unsafeStatic is not cached and
will re-render the entire template every time the value changes which may
negatively affect performance.
{% endaside %}
Add styles by defining the static styles property. Write CSS in the css tagged template literal.
{% playground-ide "articles/lit-cheat-sheet/add-styles", true %}
Related Documentation & Topics:
Styles only apply to the current element. That means you can feel free to use super generic selectors that you'd normally have to make up class names for.
{% playground-ide "articles/lit-cheat-sheet/scope-styles", true %}
Related Documentation & Topics:
To conditionally apply styles it's generally best to use classMap.
{% playground-ide "articles/lit-cheat-sheet/classes", true %}
Related Documentation & Topics:
You can share Lit stylesheets with other components by exporting them from a module and importing them into another.
{% playground-ide "articles/lit-cheat-sheet/share-styles-import", true %}
CSS Custom Properties can pierce multiple shadow roots allowing you to share values for specific properties.
{% playground-ide "articles/lit-cheat-sheet/inheriting-custom-props", true %}
Related Documentation & Topics:
CSS Shadow Parts are exposed by components with the part="<part-name>" attribute.
Shadow Parts can pierce individual shadow roots allowing you to set arbitrary styles on a given node using the ::part(<part-name>) pseudo-element.
{% playground-ide "articles/lit-cheat-sheet/css-shadow-parts", true %}
Related Documentation & Topics:
::part (external)CSS Shadow part names can only apply to the targeted element. You need to use exportparts to expose a shadow part in nested shadow roots.
You can export multiple parts by separating them with a comma (,).
You can also rename parts with a colon (:).
{% playground-ide "articles/lit-cheat-sheet/export-part", true %}
Related Documentation & Topics:
exportparts (external)::part (external)In some rare cases, you may receive trusted styles as a string and may need to apply them to a component. You can do this with native, constructable stylesheets.
{% playground-ide "articles/lit-cheat-sheet/constructable-stylesheets", true %}
{% aside "negative" %}
Be careful when using constructable stylesheets as it can open your application to privacy and security vulnerabilities.
Only use constructable stylesheets with trusted sources and strings.
{% endaside %}
Related Documentation & Topics:
CSSResultOrNativeIn some cases, you may want to import styles in the form of a CSS files rather than a Lit CSSResult or a string. Currently there
{% aside "warn" %}
This is a new feature recently added to some browsers.
Please check browser compatibility on MDN.
Due to the newness of this feature, the following example uses JavaScript and may not work in all in certain browsers.
{% endaside %}
{% playground-ide "articles/lit-cheat-sheet/import-attributes", true %}
{% aside "positive" %}
Some better-supported alternatives to this approach may include:
CSSResult
similar to
rollup-plugin-lit-css<link rel="stylesheet" href="..."> in your template, but this will
cause FOUC.{% endaside %}
Related Documentation & Topics:
with rather than
assert.<slot> elementRelated Documentation & Topics:
You can turn off the Shadow DOM by overriding the createRenderRoot() method and setting the render root to the element itself.
{% playground-ide "articles/lit-cheat-sheet/turn-off-shadow-dom", true %}
{% aside "warn" %}
This is generally not recommended, but it may sometimes be worthwhile for integration with older systems or libraries that may not be updated to work with Shadow DOM.
Since the Shadow root no longer exists, <slot> does not work and Lit will no longer handle the static styles property for you. You must decide how to handle your styles.
{% endaside %}
Related Documentation & Topics:
You can slot a component into another component's shadow DOM by using the
<slot> element. If you're familiar with React, this is similar to
props.children.
{% playground-ide "articles/lit-cheat-sheet/shadow-slotting", true %}
Related Documentation & Topics:
Slotted components use the browser's native Shadow DOM projection features. In order to keep strong, performant, and encapsulated styles, the browser vendors have placed restrictions on styling slotted content.
You can style directly-slotted elements with the ::slotted() pseudo-selector.
If you would like to style children of slotted content, you should use CSS
Custom Properties.
{% playground-ide "articles/lit-cheat-sheet/styling-slotted", true %}
Related Documentation & Topics:
delegatesFocus and other shadow root optionsYou can set shadow root options passed to Element.attachShadow() by overriding the static shadowRootOptions member.
{% playground-ide "articles/lit-cheat-sheet/shadow-root-options", true %}
Related Documentation & Topics:
shadowRootOptionsElement.prototype.attachShadow():options (external)delegatesFocus (external)Reactive properties are properties within a component that automatically trigger a re-render when they change. These properties can be set externally, from outside the component's boundary.
They also handle attributes by accepting them and converting them into corresponding properties.
You can define a reactive property with the <ts-js><span slot="ts"><code>@property</code> decorator</span><span slot="js"><code>static properties = { propertyName: {...}}</code> code block and initializing them in the <code>constructor()</code></span></ts-js>.
{% playground-ide "articles/lit-cheat-sheet/reactive-properties", true %}
Related Documentation & Topics:
Reactive state is a property that is private to the component and is not exposed to the outside world. These properties are used to store internal state of a component that should trigger a re-render of the Lit lifecycle when they change.
You can define a reactive property with the <ts-js><span slot="ts"><code>@state</code> decorator</span><span slot="js"><code>static properties = { propertyName: {state: true, ...}}</code> code block and setting the <code>state: true</code> flag in the property's info. You can initialize them in the <code>constructor()</code></span></ts-js>.
{% playground-ide "articles/lit-cheat-sheet/reactive-state", true %}
Related Documentation & Topics:
Arrays are objects in JavaScript, and Lit's default change detection uses strict
equality to determine if an array has changed. If you need to re-render a
component when an array is mutated with something like .push() or .pop(),
you will need to let Lit know that the array has changed.
The most common ways to do this are:
requestUpdate() method to manually trigger a re-render{% playground-ide "articles/lit-cheat-sheet/rerender-array-change", true %}
{% aside "warn" %}
Custom hasChanged() methods in the reactive property definition won't help
much here.
The hasChanged() function is only called when the property is set, not when
the property is mutated. This would only be helpful when an array or object has
a new reference assigned to it and you don't want to trigger a re-render.
If this is your use case, you might generally be better off using a
repeat() directive.
{% endaside %}
Related Documentation & Topics:
In advanced cases, you may need to convert an attribute value to a property in a special way and vice versa. You can do this with a custom attribute converter.
{% playground-ide "articles/lit-cheat-sheet/custom-attribute-converter", true %}
{% aside "info" "no-header" %}
Attribute converters run only when an attribute is set on the element or when
a Reactive Property is set with the reflect: true option turned on.
{% endaside %}
Related Documentation & Topics:
Sometimes it is helpful to make a property that is derived from other properties or state. The simplest way you can do is is with a native getter.
{% playground-ide "articles/lit-cheat-sheet/derived-state", true %}
Related Documentation & Topics:
If you have multiple reactive properties that depend on each other, you can
reconcile their values in the Lit's willUpdate() lifecycle method.
willUpdate() is a good place to reconcile values between properties that can
also run on the server since willUpdate() is called during Lit SSR's
server-side rendering.
{% playground-ide "articles/lit-cheat-sheet/reconcile-willupdate", true %}
willUpdate()If you have reactive properties that depend on a browser API (like localStorage
for example), you can reconcile their values in the Lit's update() lifecycle
method.
update() is a good place to reconcile values between properties that
need to access Browser APIs or the DOM. update() happens before render.
{% playground-ide "articles/lit-cheat-sheet/reconcile-update", true %}
Related Documentation & Topics:
update()If you have multiple reactive properties that depend on calculations from the
component's rendered DOM, you can reconcile their values in the Lit's
updated() lifecycle method.
updated() is a good place to reconcile values between properties that need to
since updated() is called after the component has rendered its template.
Though it is highly recommended to not update Reactive Properties in updated()
unless necessary as it may trigger re-renders after a render has just completed.
Lit is fast, but this could still be unnecessary work.
{% playground-ide "articles/lit-cheat-sheet/reconcile-updated", true %}
Related Documentation & Topics:
updated()There are two lifecycles in Lit, the native Web Component lifecycle and the lifecycle that Lit adds on top of it to help handle property and state changes.
There are more lifecycle events which can be found in the documentation, but the ones you would normally use are the following and this is their general order:
constructor – (Native custom element lifecycle)connectedCallback – (Native)willUpdate – (Lit lifecycle)update – (Lit)render – (Lit)firstUpdated – (Lit)updated – (Lit)disconnectedCallback – (Native){% aside "warn" %}
The Lit lifecycle and the native custom element lifecycle are distinct and managed separately.
This means that while they generally follow a specific order, they may intermix because the browser controls the native lifecycle, while Lit and JavaScript manage the Lit lifecycle.
For example, a component may be attached to the DOM and then removed before the
Lit lifecycle may run at all, or a component may be created with
document.createElement which would call the constructor, but if it's never
added to the DOM, the connectedCallback would never run and thus the Lit
lifecycle would never run.
{% endaside %}
Related Documentation & Topics:
constructordocument.createElement('my-element')element.innerHTML = '<my-element></my-element>'new MyElement()super()connectedCallbackelement.appendChild(element)element.innerHTML = '<my-element></my-element>'super.connectedCallback()document.willUpdatesuper.willUpdate()updatewillUpdatesuper.update() AFTER custom logicrendersuper.render()firstUpdatedsuper.firstUpdated()updatedrender and firstUpdatedsuper.updated()willUpdate or update if
possible.disconnectedCallbacksuper.connectedCallback()All Lit elements have asynchronous lifecycles. The reason for this is so that property changes (e.g. el.foo = 1; el.bar = 2;) are batched for efficiency and correctness.
You need to wait for the updateComplete promise to resolve before you can be sure that the element has finished updating its DOM.
{% playground-ide "articles/lit-cheat-sheet/update-complete", true %}
Related Documentation & Topics:
If you need to perform an asynchronous task in a Lit Element. You may want to
use the @lit/task package. It handles marrying basics of managing asynchronous
and the Lit lifecycle.
The following example fetches a Pokemon by ID from the PokeAPI based on pokemon name. To do so you:
new Task(...)this)render() method with
Task.prototype.render(){% playground-ide "articles/lit-cheat-sheet/task", true %}
Related Documentation & Topics:
A common pattern is to add event listeners to the host element in the constructor(). There is no need to remove these listeners as they are automatically cleaned up by the browser's garbage collector when the element is no longer referenced.
{% playground-ide "articles/lit-cheat-sheet/host-listeners", true %}
Related Documentation & Topics:
A common pattern is to add event listeners to the to global nodes, like document or window, in the connectedCallback and remove them in the disconnectedCallback.
{% playground-ide "articles/lit-cheat-sheet/global-listeners", true %}
Related Documentation & Topics:
The simplest way to pass data down is to use properties and attributes.
For example, you can pass data down to child components using property bindings like this:
.name=${'Steven'}
For boolean attributes, use a question mark instead of a period, like this:
?programmer=${true}.
You generally want to expose your component's external attribute and property API with <ts-js><span slot="ts"><code>@property()</code> instead of <code>@state()</code></span><span slot="js"><code>static properties = {propName: {state: false}}</code></span></ts-js>.
{% playground-ide "articles/lit-cheat-sheet/pass-data-down", true %}
Related Documentation & Topics:
To send data up the tree to ancestors, you can dispatch custom events. To emit
an event, use Element.dispatchEvent().
dispatchEvent() takes an event object as the first argument. Construct a
custom event object like this:
new CustomEvent('event-name', {detail: data, bubbles: true, composed: true})
Provide data you want to pass to ancestors in the detail property of the
event, and ancestors can react to the event by adding an event listener to the
component like this:
@event-name=${this.eventHandler}
If you want an event to bubble through shadow Roots, set composed: true.
{% playground-ide "articles/lit-cheat-sheet/data-up", true %}
Related Documentation & Topics:
If you need to pass data down to a subtree without using properties or "prop
drilling", you might want to use
@lit/context.
{% playground-ide "examples/context-consume-provide", true %}
Related Documentation & Topics:
The @query decorator allows you to access a reference to a single element in
the component's shadow DOM using the syntax of
ShadowRoot.prototype.querySelector().
In JavaScript, you can access the element using
this.shadowRoot.querySelector().
{% playground-ide "articles/lit-cheat-sheet/dom-query", true %}
{% aside "warn" %}
NOTE: DOM is typically not ready until firstUpdated is called.
This means that DOM is accessible by updated() on first render as well, but
not in constructor(), connectedCallback(), or willUpdate() until
subsequent renders.
{% endaside %}
The ref() directive is a lit-html-specific method to acquire an element
reference. The ref() directive is a good alternative when:
@query decorator (or its JS equivalent){% playground-ide "articles/lit-cheat-sheet/dom-ref", true %}
{% aside "positive" %}
The ref() directive also accepts a callback function that will be called with
the element reference as an argument when the target element is connected to the
DOM.
Though, it is generally recommended to use the @query or the
@queryAsync decorators when possible as they are generally more performant and
less-reliant on Lit.
{% endaside %}
Related Documentation & Topics:
The @queryAsync decorator is just like the @query decorator, but it waits
for the current host element to finish its update before resolving. This is
useful when you need to access an element that is rendered asynchronously by a
change in the component's state.
The JavaScript equivalent awaits the this.updateComplete promise before
calling this.shadowRoot.querySelector().
{% playground-ide "articles/lit-cheat-sheet/dom-query-async", true %}
Related Documentation & Topics:
Shadow DOM uses the <slot> element which allows you to project content from
outside the shadow root into the shadow root. You can access slotted content
using the <ts-js><span slot="ts"><code>@queryAssignedElements</code> decorator</span><span slot="js"><code>HTMLSlotElement.assignedElements()</code> method</span></ts-js>.
You give it a slot name to access, and the element selector to filter for.
{% playground-ide "articles/lit-cheat-sheet/dom-qae", true %}
Related Documentation & Topics:
assignedElements - MDN (external)HTMLSlotElement - MDN (external)<slot> - MDN (external)Signals are data structures for managing observable state. They either store a
value or compute a value based on other signals. The Lit project tries to
conform to the
Signals standard proposal via the
@lit-labs/signals package
in order to provide a cross-framework standard for reactive state management
solution.
The most common way to use signals in Lit is to use the SignalWatcher mixin.
When an accessed signal value changes, SignalWatcher will trigger the Lit
element update lifecycle. This includes signals read in shouldUpdate(),
willUpdate(), update(), render(), updated(), firstUpdated(), and
reactive controller's hostUpdate() and hostUpdated().
{% playground-ide "articles/lit-cheat-sheet/signal-watcher", true %}
Related Documentation & Topics:
@lit-labs/signals npm package (external)The watch() directive allows you to pinpoint exactly where a signal should
update the DOM without re-triggering the lit-html re-render. What this means is
that using the watch() directive will not trigger the render() unless it
triggers the change of a traditional Lit Reactive Property.
{% playground-ide "articles/lit-cheat-sheet/signals-watch-directive", true %}
{% aside "warn" "no-header" %}
This may be a helpful way to optimize performance in your Lit components, but always measure for your use case.
{% endaside %}
Related Documentation & Topics:
watch() directive (external)@lit-labs/signals npm package (external)The @lit-labs/signals package also provides an html template tag that can
be used in place of Lit's default html tag and automatically wraps any signals
in the template with a watch() directive.
{% playground-ide "articles/lit-cheat-sheet/signals-html-tag", true %}
Related Documentation & Topics:
Sometimes you need to derive a value from other signals. You can do this with
a computed() signal.
{% playground-ide "articles/lit-cheat-sheet/signals-computed", true %}
Related Documentation & Topics:
@lit-labs/signals npm package (external)The official signal-utils package currently provides an experimental
effect() function that allows you to react to signal changes and run side
effects.
{% playground-ide "articles/lit-cheat-sheet/signals-effect", true %}
{% aside "warn" %}
The effect() function from the signal-utils package is experimental.
Follow the
signal-utils package
for updates on this project.
{% endaside %}
Related Documentation & Topics:
@lit-labs/signals npm package (external)changedProperties mapIf your component needs to share a global state with another component and you do not need your component to be compatible with Lit's declarative event listener syntax, you can use a shared signal to share state across components.
Here is the scoreboard example from the Dispatch Events Up section, but using shared signals.
{% playground-ide "articles/lit-cheat-sheet/signals-share", true %}
Related Documentation & Topics: