Skip to main content

Reactive properties

Lit components receive input and store their state as JavaScript class fields or properties. Reactive properties are properties that can trigger the reactive update cycle when changed, re-rendering the component, and optionally be read or written to attributes.

Lit manages your reactive properties and their corresponding attributes. In particular:

  • Reactive updates. Lit generates a getter/setter pair for each reactive property. When a reactive property changes, the component schedules an update.
  • Attribute handling. By default, Lit sets up an observed attribute corresponding to the property, and updates the property when the attribute changes. Property values can also, optionally, be reflected back to the attribute.
  • Superclass properties. Lit automatically applies property options declared by a superclass. You don't need to redeclare properties unless you want to change options.
  • Element upgrade. If a Lit component is defined after the element is already in the DOM, Lit handles upgrade logic, ensuring that any properties set on an element before it was upgraded trigger the correct reactive side effects when the element upgrades.

Public properties and internal state

Permalink to Public properties and internal state

Public properties are part of the component's public API. In general, public properties—especially public reactive properties—should be treated as input.

The component shouldn't change its own public properties, except in response to user input. For example, a menu component might have a public selected property that can be initialized to a given value by the owner of the element, but that is updated by the component itself when the user selects an item. In these instances, the component should dispatch an event to indicate to the component's owner that the selected property changed. See Dispatching events for more details.

Lit also supports internal reactive state. Internal reactive state refers to reactive properties that aren't part of the component's API. These properties don't have a corresponding attribute, and are typically marked protected or private in TypeScript.

The component manipulates its own internal reactive state. In some cases, internal reactive state may be initialized from public properties—for example, if there is a expensive transformation between the user-visible property and the internal state.

As with public reactive properties, updating internal reactive state triggers an update cycle. For more information, see Internal reactive state.

Declare your element's public reactive properties using decorators or the static properties field.

In either case, you can pass an options object to configure features for the property.

Use the @property decorator with a class field declaration to declare a reactive property.

The argument to the @property decorators is an options object. Omitting the argument is equivalent to specifying the default value for all options.

Using decorators. Decorators are a proposed JavaScript feature, so you'll need to use a compiler like Babel or the TypeScript compiler to use decorators. See Enabling decorators for details.

Declaring properties in a static properties field

Permalink to Declaring properties in a static properties field

To declare properties in a static properties field:

An empty option object is equivalent to specifying the default value for all options.

If you're using the static properties field, initialize properties in the constructor. Class field initializers won't work in this case.

The options object can have the following properties:

attribute

Whether the property is associated with an attribute, or a custom name for the associated attribute. Default: true. If attribute is false, the converter, reflect and type options are ignored. For more information, see Setting the attribute name.

converter

A custom converter for converting between properties and attributes. If unspecified, use the default attribute converter.

hasChanged

A function called whenever the property is set to determine if the property has changed, and should trigger an update. If unspecified, LitElement uses a strict inequality check (newValue !== oldValue) to determine whether the property value has changed. For more information, see Customizing change detection.

noAccessor

Set to true to avoid generating the default property accessors. This option is rarely necessary. Default: false. For more information, see Preventing Lit from generating a property accessor.

reflect

Whether property value is reflected back to the associated attribute. Default: false. For more information, see Enabling attribute reflection.

state

Set to true to declare the property as internal reactive state. Internal reactive state triggers updates like public reactive properties, but Lit doesn't generate an attribute for it, and users shouldn't access it from outside the component. Equivalent to using the @state decorator. Default: false. For more information, see Internal reactive state.

type

When converting a string-valued attribute into a property, Lit's default attribute converter will parse the string into the type given, and vice-versa when reflecting a property to an attribute. If converter is set, this field is passed to the converter. If type is unspecified, the default converter treats it as type: String. See Using the default converter.

When using TypeScript, this field should generally match the TypeScript type declared for the field. However, the type option is used by the Lit's runtime for string serialization/deserialization, and should not be confused with a type-checking mechanism.

Omitting the options object or specifying an empty options object is equivalent to specifying the default value for all options.

Internal reactive state refers to reactive properties that are not part of the component's public API. These state properties don't have corresponding attributes, and aren't intended to be used from outside the component. Internal reactive state should be set by the component itself.

Use the @state decorator to declare internal reactive state:

Using the static properties getter, you can declare internal reactive state by using the state: true option.

Internal reactive state shouldn't be referenced from outside the component. In TypeScript, these properties should be marked as private or protected. We also recommend using a convention like a leading underscore (_) to identify private or protected properties for JavaScript users.

Internal reactive state works just like public reactive properties, except that there is no attribute associated with the property. The only option you can specify for internal reactive state is the hasChanged function.

The @state decorator can also serve as a hint to a code minifier that the property name can be changed during minification.

A property change can trigger a reactive update cycle, which causes the component to re-render its template.

When a property changes, the following sequence occurs:

  1. The property's setter is called.
  2. The setter calls the component's requestUpdate method.
  3. The property's old and new values are compared. If the property has a hasChanged function, it's called with the property's old and new values.
  4. If the property change is detected, an update is scheduled asynchronously. If an update is already scheduled, only a single update is executed.
  5. The component's update method is called, reflecting changed properties to attributes and re-rendering the component's templates.

There are many ways to hook into and modify the reactive update cycle. For more information, see Reactive update cycle.

For more information about property change detection, see Customizing change detection.

While properties are great for receiving JavaScript data as input, attributes are the standard way HTML allows configuring elements from markup, without needing to use JavaScript to set properties. Providing both a property and attribute interface for their reactive properties is a key way Lit components can be useful in a wide variety of environments, including those rendered without a client-side templating engine, such as static HTML pages served from CMSs.

By default, Lit sets up an observed attribute corresponding to each public reactive property, and updates the property when the attribute changes. Property values can also, optionally, be reflected (written back to the attribute).

While element properties can be of any type, attributes are always strings. This impacts the observed attributes and reflected attributes of non-string properties:

  • To observe an attribute (set a property from an attribute), the attribute value must be converted from a string to match the property type.

  • To reflect an attribute (set an attribute from a property), the property value must be converted to a string.

By default, Lit creates a corresponding observed attribute for all public reactive properties. The name of the observed attribute is the property name, lowercased:

To create an observed attribute with a different name, set attribute to a string:

To prevent an observed attribute from being created for a property, set attribute to false. The property will not be initialized from attributes in markup, and attribute changes won't affect it.

Internal reactive state never has an associated attribute.

An observed attribute can be used to provide an initial value for a property from markup. For example:

Lit has a default converter that handles String, Number, Boolean, Array, and Object property types.

To use the default converter, specify the type option in your property declaration:

If you don't specify a type or a custom converter for a property, it behaves as if you'd specified type: String.

The tables below shows how the default converter handles conversion for each type.

From attribute to property

TypeConversion
StringIf the element has the corresponding attribute, set the property to the attribute value.
NumberIf the element has the corresponding attribute, set the property to Number(attributeValue).
BooleanIf the element has the corresponding attribute, set the property to true.
If not, set the property to false.
Object, ArrayIf the element has the corresponding attribute, set the property value to JSON.parse(attributeValue).

For any case except Boolean, if the element doesn't have the corresponding attribute, the property keeps its default value, or undefined if no default is set.

From property to attribute

TypeConversion
String, NumberIf property is defined and non-null, set the attribute to the property value.
If property is null or undefined, remove the attribute.
BooleanIf property is truthy, create the attribute and set its value to an empty string.
If property is falsy, remove the attribute
Object, ArrrayIf property is defined and non-null, set the attribute to JSON.stringify(propertyValue).
If property is null or undefined, remove the attribute.

You can specify a custom property converter in your property declaration with the converter option:

converter can be an object or a function. If it is an object, it can have keys for fromAttribute and toAttribute:

If converter is a function, it is used in place of fromAttribute:

If no toAttribute function is supplied for a reflected attribute, the attribute is set to the property value using the default converter.

If toAttribute returns null or undefined, the attribute is removed.

You can configure a property so that whenever it changes, its value is reflected to its corresponding attribute. Reflected attributes are useful because attributes are visible to CSS, and to DOM APIs like querySelector.

For example:

When the property changes, Lit sets the corresponding attribute value as described in Using the default converter or Providing a custom converter.

Attributes should generally be considered input to the element from its owner, rather than under control of the element itself, so reflecting properties to attributes should be done sparingly. It's necessary today for cases like styling and accessibility, but this is likely to change as the platform adds features like the :state pseudo selector and the Accessibility Object Model, which fill these gaps.

Reflecting properties of type object or array is not recommended. This can cause large objects to serialize to the DOM which can result in poor performance.

Lit tracks reflection state during updates. You may have realized that if property changes are reflected to an attribute and attribute changes update the property, it has the potential to create an infinite loop. However, Lit tracks when properties and attributes are set specifically to prevent this from happening

By default, LitElement generates a getter/setter pair for all reactive properties. The setter is invoked whenever you set the property:

Generated accessors automatically call requestUpdate(), initiating an update if one has not already begun.

To specify how getting and setting works for a property, you can define your own getter/setter pair. For example:

To use custom property accessors with the @property or @state decorators, put the decorator on the getter, as shown above.

The setters that Lit generates automatically call requestUpdate(). If you write your own setter you must call requestUpdate() manually, supplying the property name and its old value.

In most cases, you do not need to create custom property accessors. To compute values from existing properties, we recommend using the willUpdate callback, which allows you to set values during the update cycle without triggering an additional update. To perform a custom action after the element updates, we recommend using the updated callback. A custom setter can be used in rare cases when it's important to synchronously validate any value the user sets.

If your class defines its own accessors for a property, Lit will not overwrite them with generated accessors. If your class does not define accessors for a property, Lit will generate them, even if a superclass has defined the property or accessors.

In rare cases, a subclass may need to change or add property options for a property that exists on its superclass.

To prevent Lit from generating a property accessor that overwrites the superclass's defined accessor, set noAccessor to true in the property declaration:

You don't need to set noAccessor when defining your own accessors.

All reactive properties have a function, hasChanged(), which is called when the property is set.

hasChanged compares the property's old and new values, and evaluates whether or not the property has changed. If hasChanged() returns true, Lit starts an element update if one is not already scheduled. For more information on updates, see Reactive update cycle .

The default implementation of hasChanged() uses a strict inequality comparison: hasChanged() returns true if newVal !== oldVal.

To customize hasChanged() for a property, specify it as a property option:

In the following example, hasChanged() only returns true for odd values.