0
# Property System
1
2
The property system provides reactive property management with decorators, type conversion, and change detection for building reactive custom elements. Properties automatically trigger re-renders when changed and can be synchronized with attributes.
3
4
## Capabilities
5
6
### Property Decorator
7
8
Declares a reactive property that triggers updates when changed.
9
10
```typescript { .api }
11
/**
12
* Declares a reactive property that triggers updates when changed
13
* @param options Configuration options for the property
14
* @returns Property decorator function
15
*/
16
function property(options?: PropertyDeclaration): PropertyDecorator;
17
18
interface PropertyDeclaration {
19
/**
20
* Type hint for attribute conversion (String, Number, Boolean, Array, Object)
21
*/
22
type?: TypeHint;
23
24
/**
25
* Attribute name to observe, or false to disable attribute observation
26
*/
27
attribute?: boolean | string;
28
29
/**
30
* Whether to reflect property value back to attribute
31
*/
32
reflect?: boolean;
33
34
/**
35
* Custom converter for attribute/property conversion
36
*/
37
converter?: AttributeConverter;
38
39
/**
40
* Whether to skip creating a property accessor
41
*/
42
noAccessor?: boolean;
43
44
/**
45
* Function to determine if property has changed
46
*/
47
hasChanged?: HasChanged;
48
}
49
```
50
51
**Usage Examples:**
52
53
```typescript
54
import { LitElement, html, property } from "lit-element";
55
56
class MyElement extends LitElement {
57
@property({ type: String })
58
name = "World";
59
60
@property({ type: Number, reflect: true })
61
count = 0;
62
63
@property({ type: Boolean, attribute: "is-active" })
64
isActive = false;
65
66
@property({ type: Array })
67
items: string[] = [];
68
69
@property({
70
type: String,
71
hasChanged: (newVal, oldVal) => newVal?.toLowerCase() !== oldVal?.toLowerCase()
72
})
73
caseSensitive = "";
74
75
render() {
76
return html`
77
<div>
78
<p>Name: ${this.name}</p>
79
<p>Count: ${this.count}</p>
80
<p>Active: ${this.isActive}</p>
81
<p>Items: ${this.items.join(", ")}</p>
82
</div>
83
`;
84
}
85
}
86
```
87
88
### State Decorator
89
90
Declares internal reactive state that doesn't reflect to attributes.
91
92
```typescript { .api }
93
/**
94
* Declares internal reactive state that doesn't reflect to attributes
95
* @param options Configuration options for the state property
96
* @returns Property decorator function
97
*/
98
function state(options?: StateDeclaration): PropertyDecorator;
99
100
interface StateDeclaration {
101
/**
102
* Function to determine if state has changed
103
*/
104
hasChanged?: HasChanged;
105
}
106
```
107
108
**Usage Examples:**
109
110
```typescript
111
import { LitElement, html, property, state } from "lit-element";
112
113
class MyElement extends LitElement {
114
@property({ type: String })
115
title = "";
116
117
@state()
118
private _expanded = false;
119
120
@state()
121
private _loading = false;
122
123
private _toggle() {
124
this._expanded = !this._expanded;
125
}
126
127
render() {
128
return html`
129
<div>
130
<h2 @click=${this._toggle}>${this.title}</h2>
131
${this._expanded ? html`
132
<div class="content">
133
${this._loading ? html`<p>Loading...</p>` : html`<p>Content here</p>`}
134
</div>
135
` : ''}
136
</div>
137
`;
138
}
139
}
140
```
141
142
### Static Properties
143
144
Alternative to decorators for JavaScript or when decorators aren't preferred.
145
146
```typescript { .api }
147
/**
148
* Static property declarations for reactive properties
149
*/
150
static properties: PropertyDeclarations;
151
152
interface PropertyDeclarations {
153
[key: string]: PropertyDeclaration;
154
}
155
```
156
157
**Usage Examples:**
158
159
```typescript
160
import { LitElement, html } from "lit-element";
161
162
class MyElement extends LitElement {
163
static properties = {
164
name: { type: String },
165
count: { type: Number, reflect: true },
166
items: { type: Array },
167
_internal: { state: true }
168
};
169
170
constructor() {
171
super();
172
this.name = "World";
173
this.count = 0;
174
this.items = [];
175
this._internal = false;
176
}
177
178
render() {
179
return html`<p>Hello, ${this.name}!</p>`;
180
}
181
}
182
```
183
184
### Type Hints
185
186
Type hints for automatic attribute conversion.
187
188
```typescript { .api }
189
/**
190
* Type hint for property conversion
191
*/
192
type TypeHint =
193
| typeof String // Converts to/from string attributes
194
| typeof Number // Converts to/from numeric attributes
195
| typeof Boolean // Converts to/from boolean attributes (presence-based)
196
| typeof Array // JSON serialization for complex types
197
| typeof Object; // JSON serialization for complex types
198
```
199
200
### Attribute Converter
201
202
Custom conversion between attributes and properties.
203
204
```typescript { .api }
205
/**
206
* Interface for custom attribute/property conversion
207
*/
208
interface AttributeConverter<Type = unknown, TypeHint = unknown> {
209
/**
210
* Convert attribute value to property value
211
* @param value Attribute value (string or null)
212
* @param type Type hint for conversion
213
* @returns Converted property value
214
*/
215
fromAttribute?(value: string | null, type?: TypeHint): Type;
216
217
/**
218
* Convert property value to attribute value
219
* @param value Property value
220
* @param type Type hint for conversion
221
* @returns Converted attribute value (string, number, boolean, or null)
222
*/
223
toAttribute?(value: Type, type?: TypeHint): unknown;
224
}
225
```
226
227
**Usage Examples:**
228
229
```typescript
230
import { LitElement, html, property } from "lit-element";
231
232
const dateConverter = {
233
fromAttribute: (value: string | null) => {
234
return value ? new Date(value) : null;
235
},
236
toAttribute: (value: Date | null) => {
237
return value ? value.toISOString() : null;
238
}
239
};
240
241
class MyElement extends LitElement {
242
@property({ converter: dateConverter })
243
createdAt: Date | null = null;
244
245
render() {
246
return html`
247
<p>Created: ${this.createdAt?.toLocaleDateString() || 'Not set'}</p>
248
`;
249
}
250
}
251
```
252
253
### Has Changed Function
254
255
Function to determine if a property value has changed.
256
257
```typescript { .api }
258
/**
259
* Function to determine if property has changed
260
* @param value New property value
261
* @param oldValue Previous property value
262
* @returns True if property has changed
263
*/
264
interface HasChanged {
265
(value: unknown, oldValue: unknown): boolean;
266
}
267
268
/**
269
* Default hasChanged implementation using strict inequality
270
*/
271
function notEqual(value: unknown, oldValue: unknown): boolean;
272
```
273
274
### Default Converter
275
276
Default converter used for standard type conversion.
277
278
```typescript { .api }
279
/**
280
* Default attribute converter with support for standard types
281
*/
282
const defaultConverter: AttributeConverter;
283
```
284
285
**Usage Examples:**
286
287
```typescript
288
import { LitElement, html, property, notEqual } from "lit-element";
289
290
class MyElement extends LitElement {
291
@property({
292
type: String,
293
hasChanged: (newVal, oldVal) => {
294
// Case-insensitive comparison
295
return newVal?.toLowerCase() !== oldVal?.toLowerCase();
296
}
297
})
298
name = "";
299
300
@property({
301
type: Number,
302
hasChanged: (newVal, oldVal) => {
303
// Only update if difference is significant
304
return Math.abs(Number(newVal) - Number(oldVal)) > 0.1;
305
}
306
})
307
value = 0;
308
309
render() {
310
return html`
311
<div>
312
<p>Name: ${this.name}</p>
313
<p>Value: ${this.value}</p>
314
</div>
315
`;
316
}
317
}
318
```
319
320
### Property Change Detection
321
322
Understanding how property changes are detected and handled.
323
324
```typescript { .api }
325
/**
326
* Request an update which will trigger the update lifecycle
327
* @param name Property name that changed
328
* @param oldValue Previous value of the property
329
* @param options Additional options for the update request
330
*/
331
requestUpdate(name?: PropertyKey, oldValue?: unknown, options?: RequestUpdateOptions): void;
332
333
interface RequestUpdateOptions {
334
/**
335
* Whether to trigger an update even if hasChanged returns false
336
*/
337
force?: boolean;
338
}
339
```
340
341
**Usage Examples:**
342
343
```typescript
344
import { LitElement, html } from "lit-element";
345
346
class MyElement extends LitElement {
347
private _data: any[] = [];
348
349
get data() {
350
return this._data;
351
}
352
353
set data(value: any[]) {
354
const oldValue = this._data;
355
this._data = value;
356
// Manually request update for array property
357
this.requestUpdate('data', oldValue);
358
}
359
360
addItem(item: any) {
361
this._data.push(item);
362
// Request update after mutating array
363
this.requestUpdate('data', this._data);
364
}
365
366
render() {
367
return html`
368
<ul>
369
${this.data.map(item => html`<li>${item}</li>`)}
370
</ul>
371
`;
372
}
373
}
374
```