Skip to main content

Upgrade guide

Lit 2.0 is designed to work with most code written for LitElement 2.x and lit-html 1.x. There are a small number of changes required to migrate your code to Lit 2.0. The high-level changes required include:

  1. Updating npm packages and import paths.
  2. Loading polyfill-support script when loading the web components polyfills.
  3. Updating any custom directive implementations to use new class-based API and associated helpers.
  4. Updating code to renamed APIs.
  5. Adapting to minor breaking changes, mostly in uncommon cases.

The following sections will go through each of these changes in detail.

Lit 2.0 ships with a one-stop-shop lit package, which consolidates lit-html and lit-element into an easy-to-use package. Use the following commands to upgrade:

And re-write your module imports appropriately:

From:

To:

Although the lit-element@^3 and lit-html@^2 packages should be largely backward-compatible, we recommend updating to the lit package as the other packages are moving towards eventual deprecation.

The previous version of lit-element exported all TypeScript decorators from the main module. In Lit 2.0, these have been moved to a separate module, to enable smaller bundle sizes when the decorators are unused.

From:

To:

Built-in lit-html directives are also now exported from the lit package.

From:

To:

If using lit-html standalone (outside of LitElement), you can import standalone-specific API's like render from the lit/html.js entrypoint:

From:

To:

Load polyfill-support when using web components polyfills

Permalink to “Load polyfill-support when using web components polyfills”

Lit 2.0 still supports the same browsers down to IE11. However, given the broad adoption of Web Components APIs in modern browsers, we have taken the opportunity to move all of the code required for interfacing with the web components polyfills out of the core libraries and into an opt-in support file, so that the tax for supporting older browsers is only paid when required.

In general, any time you use the web components polyfills, you should also load the lit/polyfill-support.js support file once on the page, similar to a polyfill. For example:

If using @web/test-runner or @web/dev-server with the legacyPlugin for development, adding the following configuration to your web-test-runner.config.js or web-dev-server.config.js file will configure it to automatically inject the support file when needed:

The following advanced API's have been renamed in Lit 2.0. It should be safe to simply rename these across your codebase if used:

Previous nameNew nameNotes
UpdatingElementReactiveElementThe base class underpinning LitElement. Naming now aligns with terminology we use to describe its reactive lifecycle.
@internalProperty@stateDecorator for LitElement / ReactiveElement used to denote private state that trigger updates, as opposed to public properties on the element settable by the user which use the @property decorator.
static getStyles()static finalizeStyles(styles)Method on LitElement and ReactiveElement class used for overriding style processing. Note it now also takes an argument reflecting the static styles for the class.
_getUpdateComplete()getUpdateComplete()Method on LitElement and ReactiveElement class used for overriding the updateComplete promise
NodePartChildPartTypically only used in directive code; see below.

While the API for using directives should be 100% backward-compatible with 1.x, there is a breaking change to how custom directives are authored. The API change improves ergonomics around making stateful directives while providing a clear pattern for SSR-compatible directives: only render will be called on the server, while update will not be.

ConceptPrevious APINew API
Code idiomFunction that takes directive arguments, and returns function that takes part and returns valueClass that extends Directive with update & render methods which accept directive arguments
Declarative renderingPass value to part.setValue()Return value from render() method
DOM manipulationImplement in directive functionImplement in update() method
StateStored in WeakMap keyed on partStored in class instance fields
Part validationinstanceof check on part in every renderpart.type check in constructor
Async updatespart.setValue(v);
part.commit();
Extend AsyncDirective instead of Directive and call this.setValue(v)

Below is an example of a lit-html 1.x directive, and how to migrate it to the new API:

1.x Directive API:

2.0 Directive API:

For completeness, the following is a list of minor but notable breaking changes that you may need to adapt your code to. We expect these changes to affect relatively few users.

  • For simplicity, requestUpdate no longer returns a Promise. Instead await the updateComplete Promise.
  • Errors that occur during the update cycle were previously squelched to allow subsequent updates to proceed normally. Now errors are re-fired asynchronously so they can be detected. Errors can be observed via an unhandledrejection event handler on window.
  • Creation of shadowRoot via createRenderRoot and support for applying static styles to the shadowRoot has moved from LitElement to ReactiveElement.
  • The createRenderRoot method is now called just before the first update rather than in the constructor. Element code can not assume the renderRoot exists before the element hasUpdated. This change was made for compatibility with server-side rendering.
  • ReactiveElement's initialize method has been removed. This work is now done in the element constructor.
  • The static render method on the LitElement base class has been removed. This was primarily used for implementing ShadyDOM integration, and was not intended as a user-overridable method. ShadyDOM integration is now achieved via the polyfill-support module.
  • When a property declaration is reflect: true and its toAttribute function returns undefined the attribute is now removed where previously it was left unchanged (#872).
  • The dirty check in attributeChangedCallback has been removed. While technically breaking, in practice it should very rarely be (#699).
  • LitElement's adoptStyles method has been removed. Styling is now adopted in createRenderRoot. This method may be overridden to customize this behavior.
  • Removed requestUpdateInternal. The requestUpdate method is now identical to this method and should be used instead.
  • render() no longer clears the container it's rendered to on first render. It now appends to the container by default.
  • Expressions in comments are not rendered or updated.
  • Template caching happens per call site, not per template-tag/call-site pair. This means some rare forms of highly dynamic template tags are no longer supported.
  • Arrays and other iterables passed to attribute bindings are not specially handled. Arrays will be rendered with their default toString representation. This means that html`<div class=${['a', 'b']}> will render <div class="a,b"> instead of <div class="a b">. To get the old behavior, use array.join(' ').
  • The templateFactory option of RenderOptions has been removed.
  • TemplateProcessor has been removed.
  • Symbols are not converted to a string before mutating DOM, so passing a Symbol to an attribute or text binding will result in an exception.
  • The ifDefined directive in an attribute expression now removes the attribute for both null and undefined, not just undefined.
  • Rendering the value nothing to an attribute expression causes the attribute to be removed—even if there are multiple expressions in the attribute value position and only one is nothing. For example given src="${baseurl}/${filename}", the src attribute is removed if either baseurl or filename evaluate to nothing.
  • Rendering the value nothing, null, or undefined in the unsafeHTML or unsafeSVG directive will now result in no content being rendered (previously it would render '[object Object]', 'null', or 'undefined', respectively).