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.