Skip to content

Lit Properties

This lesson explains Lit properties and how they drive reactive updates in Lit components, including public properties, internal state, attribute reflection, and property configuration options.

What Are Lit Properties?

Lit properties are reactive fields on a Lit component. When a declared property changes, Lit schedules a re-render and updates the component template efficiently.

Lit distinguishes between public properties, which form the component API, and internal state, which is private to the component implementation.

Concept Description
@property() Public reactive property
@state() Internal reactive state
static properties Non-decorator property declaration
reflect: true Sync property value to attribute
hasChanged() Custom change detection
Main Benefit Reactive component updates

Basic Lit Property Example

import { LitElement, html } from "lit";
import { customElement, property } from "lit/decorators.js";

@customElement("status-badge")
class StatusBadge extends LitElement {
  @property()
  label = "Pending";

  @property()
  variant = "info";

  render() {
    return html`
      <span class="badge ${this.variant}">
        ${this.label}
      </span>
    `;
  }
}
const badge = document.querySelector("status-badge");
badge.label = "Approved";
badge.variant = "success";

Changing label or variant triggers a Lit re-render automatically.

The @property Decorator

@property({ type: String })
name = "";

@property({ type: Boolean, reflect: true })
disabled = false;

@property({ type: Array })
items = [];
Option Purpose
type Converts attribute strings to JS types
attribute Custom attribute name or false to disable
reflect Writes property value back to attribute
hasChanged Controls whether a new value triggers update

Declaring Properties Without Decorators

class UserCard extends LitElement {
  static properties = {
    name: { type: String },
    active: { type: Boolean, reflect: true },
    roles: { type: Array }
  };

  constructor() {
    super();
    this.name = "";
    this.active = false;
    this.roles = [];
  }
}

You can declare properties with static properties instead of decorators. This is common in plain JavaScript Lit projects.

Internal State with @state

import { state } from "lit/decorators.js";

@customElement("counter-button")
class CounterButton extends LitElement {
  @state()
  private count = 0;

  handleClick() {
    this.count += 1;
  }

  render() {
    return html`
      <button type="button" @click="${this.handleClick}">
        Count: ${this.count}
      </button>
    `;
  }
}

Use @state() for internal reactive fields that should not be part of the public component API.

Reflecting Properties to Attributes

@property({ type: Boolean, reflect: true })
open = false;

render() {
  return html`
    <section class="panel" ?hidden="${!this.open}">
      <slot></slot>
    </section>
  `;
}
<toggle-panel open></toggle-panel>

Reflection keeps properties visible in HTML and useful for CSS, SSR, and declarative configuration.

Custom Attribute Names

@property({
  attribute: "button-label",
  type: String
})
label = "Click me";
<app-button button-label="Save Profile"></app-button>

Use the attribute option when the HTML attribute name should differ from the JavaScript property name.

Property Type Conversion

@property({ type: Number })
count = 0;

@property({ type: Boolean })
disabled = false;

@property({ type: Object })
user = { name: "Guest" };

Lit converts attribute strings into the declared property type. Complex objects and arrays are usually set as properties from JavaScript rather than attributes.

Custom Change Detection with hasChanged

@property({
  hasChanged(newValue, oldValue) {
    return newValue !== oldValue;
  }
})
message = "";

Override hasChanged when you want fine control over whether a property update should trigger a re-render.

@property vs @state

Feature @property @state
Purpose Public component API Internal component state
Attribute support Yes, by default No attribute mapping
Best For Inputs from parent apps Counters, toggles, local UI state
Re-render Yes, on change Yes, on change

Using Lit Properties in Frameworks

// React
<user-card user={{ name: "Alex", role: "admin" }} active />

// Vue
// <user-card :user="currentUser" :active="true" />

// Plain JS
card.user = { name: "Alex", role: "admin" };
card.active = true;

Frameworks typically set Lit properties directly. Design your public API around properties for rich values and reflected attributes for simple declarative config.

When to Use Lit Properties

  • You need reactive updates in Lit components.
  • Parent apps pass configuration into your component.
  • You want attribute reflection for HTML and CSS usage.
  • You manage internal UI state with @state().
  • You need typed property conversion from attributes.
  • You are building a reusable Lit component library.

Common Lit Property Use Cases

  • label, variant, and size on UI primitives.
  • open and disabled on interactive widgets.
  • items and user object inputs for data display.
  • count and selected internal state values.
  • Reflected boolean states for styling and accessibility.
  • Framework-driven props on shared design system components.

Lit Property Best Practices

  • Use @property() for public API fields.
  • Use @state() for private reactive UI state.
  • Initialize defaults in field declarations or the constructor.
  • Reflect only properties that benefit HTML, CSS, or SSR.
  • Choose correct type options for attribute conversion.
  • Document which properties are public and which are internal.
  • Keep property names consistent across your component library.

Common Lit Property Mistakes

  • Using @state() for values that should be public API.
  • Expecting complex objects to work well through attributes alone.
  • Forgetting to initialize array or object properties.
  • Reflecting every property without a real need.
  • Mutating objects in place without triggering updates.
  • Not choosing the right type for attribute conversion.
  • Mixing public and internal fields without a clear pattern.

Key Takeaways

  • Lit properties make components reactive and efficient.
  • @property() is for public inputs; @state() is for internal state.
  • Reflection, types, and custom attribute names configure the public API.
  • Property changes automatically trigger Lit re-renders.
  • Good property design is central to reusable Lit components.

Pro Tip

Design Lit components with a clear split: reflected @property() fields for simple declarative config, and regular properties for rich framework-driven data.