or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-element.mdcss-styling.mddecorators.mddirectives.mdindex.mdpolyfill-support.mdproperty-system.mdtemplate-system.md
tile.json

decorators.mddocs/

0

# Decorators

1

2

TypeScript decorators for enhanced development experience with declarative property and element configuration. Decorators provide a clean, declarative syntax for configuring reactive properties and DOM queries.

3

4

## Capabilities

5

6

### Custom Element Decorator

7

8

Registers a custom element with the browser's custom element registry.

9

10

```typescript { .api }

11

/**

12

* Class decorator to register a custom element

13

* @param tagName The tag name for the custom element (must contain a hyphen)

14

* @returns Class decorator function

15

*/

16

function customElement(tagName: string): ClassDecorator;

17

```

18

19

**Usage Examples:**

20

21

```typescript

22

import { LitElement, html, customElement } from "lit-element";

23

24

@customElement("my-button")

25

class MyButton extends LitElement {

26

render() {

27

return html`

28

<button>

29

<slot></slot>

30

</button>

31

`;

32

}

33

}

34

35

@customElement("user-card")

36

class UserCard extends LitElement {

37

render() {

38

return html`

39

<div class="card">

40

<slot name="avatar"></slot>

41

<slot name="name"></slot>

42

<slot name="bio"></slot>

43

</div>

44

`;

45

}

46

}

47

48

// Elements are automatically registered and can be used in HTML

49

// <my-button>Click me</my-button>

50

// <user-card>

51

// <img slot="avatar" src="..." />

52

// <h2 slot="name">John Doe</h2>

53

// <p slot="bio">Software developer</p>

54

// </user-card>

55

```

56

57

### Property Decorator

58

59

Declares a reactive property that triggers updates when changed.

60

61

```typescript { .api }

62

/**

63

* Property decorator for reactive properties

64

* @param options Configuration options for the property

65

* @returns Property decorator function

66

*/

67

function property(options?: PropertyDeclaration): PropertyDecorator;

68

```

69

70

**Usage Examples:**

71

72

```typescript

73

import { LitElement, html, customElement, property } from "lit-element";

74

75

@customElement("my-element")

76

class MyElement extends LitElement {

77

@property({ type: String })

78

name = "World";

79

80

@property({ type: Number })

81

count = 0;

82

83

@property({ type: Boolean })

84

disabled = false;

85

86

@property({ type: Array })

87

items: string[] = [];

88

89

@property({ type: Object })

90

config: any = {};

91

92

render() {

93

return html`

94

<div>

95

<h1>Hello, ${this.name}!</h1>

96

<p>Count: ${this.count}</p>

97

<button ?disabled=${this.disabled}>

98

${this.disabled ? 'Disabled' : 'Enabled'}

99

</button>

100

<ul>

101

${this.items.map(item => html`<li>${item}</li>`)}

102

</ul>

103

</div>

104

`;

105

}

106

}

107

```

108

109

### State Decorator

110

111

Declares internal reactive state that doesn't reflect to attributes.

112

113

```typescript { .api }

114

/**

115

* Property decorator for internal reactive state

116

* @param options Configuration options for the state property

117

* @returns Property decorator function

118

*/

119

function state(options?: StateDeclaration): PropertyDecorator;

120

```

121

122

**Usage Examples:**

123

124

```typescript

125

import { LitElement, html, customElement, property, state } from "lit-element";

126

127

@customElement("toggle-element")

128

class ToggleElement extends LitElement {

129

@property({ type: String })

130

label = "Toggle";

131

132

@state()

133

private _expanded = false;

134

135

@state()

136

private _loading = false;

137

138

@state()

139

private _data: any[] = [];

140

141

private async _toggle() {

142

this._loading = true;

143

144

if (!this._expanded) {

145

// Simulate loading data

146

await new Promise(resolve => setTimeout(resolve, 1000));

147

this._data = ['Item 1', 'Item 2', 'Item 3'];

148

}

149

150

this._expanded = !this._expanded;

151

this._loading = false;

152

}

153

154

render() {

155

return html`

156

<div>

157

<button @click=${this._toggle} ?disabled=${this._loading}>

158

${this._loading ? 'Loading...' : this.label}

159

</button>

160

161

${this._expanded ? html`

162

<div class="content">

163

<ul>

164

${this._data.map(item => html`<li>${item}</li>`)}

165

</ul>

166

</div>

167

` : ''}

168

</div>

169

`;

170

}

171

}

172

```

173

174

### Query Decorator

175

176

Queries the render root for an element matching a CSS selector.

177

178

```typescript { .api }

179

/**

180

* Property decorator for DOM queries

181

* @param selector CSS selector to query for

182

* @param cache Whether to cache the query result

183

* @returns Property decorator function

184

*/

185

function query(selector: string, cache?: boolean): PropertyDecorator;

186

```

187

188

**Usage Examples:**

189

190

```typescript

191

import { LitElement, html, customElement, query } from "lit-element";

192

193

@customElement("form-element")

194

class FormElement extends LitElement {

195

@query('#username')

196

usernameInput!: HTMLInputElement;

197

198

@query('button[type="submit"]')

199

submitButton!: HTMLButtonElement;

200

201

@query('.error-message')

202

errorMessage?: HTMLElement;

203

204

@query('form', true) // cached query

205

form!: HTMLFormElement;

206

207

private _handleSubmit(e: Event) {

208

e.preventDefault();

209

210

const username = this.usernameInput.value;

211

if (!username.trim()) {

212

if (this.errorMessage) {

213

this.errorMessage.textContent = 'Username is required';

214

this.errorMessage.style.display = 'block';

215

}

216

return;

217

}

218

219

// Hide error message

220

if (this.errorMessage) {

221

this.errorMessage.style.display = 'none';

222

}

223

224

// Disable submit button

225

this.submitButton.disabled = true;

226

227

console.log('Submitting:', username);

228

}

229

230

render() {

231

return html`

232

<form @submit=${this._handleSubmit}>

233

<div>

234

<label for="username">Username:</label>

235

<input id="username" type="text" required />

236

</div>

237

<div class="error-message" style="display: none; color: red;"></div>

238

<button type="submit">Submit</button>

239

</form>

240

`;

241

}

242

}

243

```

244

245

### Query All Decorator

246

247

Queries the render root for all elements matching a CSS selector.

248

249

```typescript { .api }

250

/**

251

* Property decorator for DOM queries returning NodeList

252

* @param selector CSS selector to query for

253

* @returns Property decorator function

254

*/

255

function queryAll(selector: string): PropertyDecorator;

256

```

257

258

**Usage Examples:**

259

260

```typescript

261

import { LitElement, html, customElement, queryAll } from "lit-element";

262

263

@customElement("list-element")

264

class ListElement extends LitElement {

265

@queryAll('.item')

266

items!: NodeListOf<HTMLElement>;

267

268

@queryAll('input[type="checkbox"]')

269

checkboxes!: NodeListOf<HTMLInputElement>;

270

271

@queryAll('.draggable')

272

draggableElements!: NodeListOf<HTMLElement>;

273

274

private _selectAll() {

275

this.checkboxes.forEach(checkbox => {

276

checkbox.checked = true;

277

});

278

}

279

280

private _clearAll() {

281

this.checkboxes.forEach(checkbox => {

282

checkbox.checked = false;

283

});

284

}

285

286

private _highlightAll() {

287

this.items.forEach(item => {

288

item.classList.add('highlighted');

289

});

290

}

291

292

render() {

293

return html`

294

<div>

295

<div class="controls">

296

<button @click=${this._selectAll}>Select All</button>

297

<button @click=${this._clearAll}>Clear All</button>

298

<button @click=${this._highlightAll}>Highlight All</button>

299

</div>

300

301

<div class="item">

302

<input type="checkbox" /> Item 1

303

</div>

304

<div class="item">

305

<input type="checkbox" /> Item 2

306

</div>

307

<div class="item">

308

<input type="checkbox" /> Item 3

309

</div>

310

</div>

311

`;

312

}

313

}

314

```

315

316

### Query Async Decorator

317

318

Performs an async query that resolves when the element is found.

319

320

```typescript { .api }

321

/**

322

* Property decorator for async DOM queries

323

* @param selector CSS selector to query for

324

* @returns Property decorator function returning Promise<Element>

325

*/

326

function queryAsync(selector: string): PropertyDecorator;

327

```

328

329

**Usage Examples:**

330

331

```typescript

332

import { LitElement, html, customElement, queryAsync } from "lit-element";

333

334

@customElement("async-element")

335

class AsyncElement extends LitElement {

336

@queryAsync('#dynamic-content')

337

dynamicContent!: Promise<HTMLElement>;

338

339

@queryAsync('.lazy-loaded')

340

lazyElement!: Promise<HTMLElement>;

341

342

private async _handleDynamicContent() {

343

try {

344

const element = await this.dynamicContent;

345

element.textContent = 'Content loaded!';

346

element.style.color = 'green';

347

} catch (error) {

348

console.error('Failed to find dynamic content:', error);

349

}

350

}

351

352

private async _loadContent() {

353

// Trigger re-render to add the dynamic element

354

this.requestUpdate();

355

356

// Wait for the element to be available

357

await this._handleDynamicContent();

358

}

359

360

render() {

361

return html`

362

<div>

363

<button @click=${this._loadContent}>Load Content</button>

364

<div id="dynamic-content">Loading...</div>

365

</div>

366

`;

367

}

368

}

369

```

370

371

### Query Assigned Elements Decorator

372

373

Queries for elements assigned to a slot.

374

375

```typescript { .api }

376

/**

377

* Property decorator for querying slot assigned elements

378

* @param options Query options including slot name and selector

379

* @returns Property decorator function

380

*/

381

function queryAssignedElements(options?: QueryAssignedElementsOptions): PropertyDecorator;

382

383

interface QueryAssignedElementsOptions {

384

slot?: string;

385

selector?: string;

386

flatten?: boolean;

387

}

388

```

389

390

**Usage Examples:**

391

392

```typescript

393

import { LitElement, html, customElement, queryAssignedElements } from "lit-element";

394

395

@customElement("tab-container")

396

class TabContainer extends LitElement {

397

@queryAssignedElements({ slot: 'tab' })

398

tabs!: HTMLElement[];

399

400

@queryAssignedElements({ slot: 'panel' })

401

panels!: HTMLElement[];

402

403

@queryAssignedElements({ selector: '.special' })

404

specialElements!: HTMLElement[];

405

406

private _activeTab = 0;

407

408

private _selectTab(index: number) {

409

this._activeTab = index;

410

411

// Update tab states

412

this.tabs.forEach((tab, i) => {

413

tab.classList.toggle('active', i === index);

414

});

415

416

// Update panel visibility

417

this.panels.forEach((panel, i) => {

418

panel.style.display = i === index ? 'block' : 'none';

419

});

420

}

421

422

render() {

423

return html`

424

<div class="tab-header">

425

<slot name="tab" @slotchange=${this._handleTabsChange}></slot>

426

</div>

427

<div class="tab-content">

428

<slot name="panel" @slotchange=${this._handlePanelsChange}></slot>

429

</div>

430

<div class="special-content">

431

<slot @slotchange=${this._handleSpecialChange}></slot>

432

</div>

433

`;

434

}

435

436

private _handleTabsChange() {

437

this.tabs.forEach((tab, index) => {

438

tab.addEventListener('click', () => this._selectTab(index));

439

});

440

}

441

442

private _handlePanelsChange() {

443

this._selectTab(this._activeTab);

444

}

445

446

private _handleSpecialChange() {

447

console.log('Special elements:', this.specialElements);

448

}

449

}

450

451

// Usage:

452

// <tab-container>

453

// <button slot="tab">Tab 1</button>

454

// <button slot="tab">Tab 2</button>

455

// <div slot="panel">Panel 1 content</div>

456

// <div slot="panel">Panel 2 content</div>

457

// <div class="special">Special content</div>

458

// </tab-container>

459

```

460

461

### Query Assigned Nodes Decorator

462

463

Queries for nodes (including text nodes) assigned to a slot.

464

465

```typescript { .api }

466

/**

467

* Property decorator for querying slot assigned nodes

468

* @param options Query options including slot name and flatten

469

* @returns Property decorator function

470

*/

471

function queryAssignedNodes(options?: QueryAssignedNodesOptions): PropertyDecorator;

472

473

interface QueryAssignedNodesOptions {

474

slot?: string;

475

flatten?: boolean;

476

}

477

```

478

479

**Usage Examples:**

480

481

```typescript

482

import { LitElement, html, customElement, queryAssignedNodes } from "lit-element";

483

484

@customElement("content-analyzer")

485

class ContentAnalyzer extends LitElement {

486

@queryAssignedNodes()

487

allNodes!: Node[];

488

489

@queryAssignedNodes({ slot: 'header' })

490

headerNodes!: Node[];

491

492

@queryAssignedNodes({ flatten: true })

493

flattenedNodes!: Node[];

494

495

private _analyzeContent() {

496

console.log('All nodes:', this.allNodes);

497

console.log('Text nodes:', this.allNodes.filter(node => node.nodeType === Node.TEXT_NODE));

498

console.log('Element nodes:', this.allNodes.filter(node => node.nodeType === Node.ELEMENT_NODE));

499

console.log('Header nodes:', this.headerNodes);

500

}

501

502

render() {

503

return html`

504

<div>

505

<button @click=${this._analyzeContent}>Analyze Content</button>

506

<div class="header-section">

507

<slot name="header" @slotchange=${this._handleSlotChange}></slot>

508

</div>

509

<div class="main-content">

510

<slot @slotchange=${this._handleSlotChange}></slot>

511

</div>

512

</div>

513

`;

514

}

515

516

private _handleSlotChange() {

517

this._analyzeContent();

518

}

519

}

520

```

521

522

### Event Options Decorator

523

524

Configures event listener options for methods.

525

526

```typescript { .api }

527

/**

528

* Method decorator for configuring event listener options

529

* @param options Event listener options

530

* @returns Method decorator function

531

*/

532

function eventOptions(options: AddEventListenerOptions): MethodDecorator;

533

534

interface AddEventListenerOptions {

535

capture?: boolean;

536

once?: boolean;

537

passive?: boolean;

538

signal?: AbortSignal;

539

}

540

```

541

542

**Usage Examples:**

543

544

```typescript

545

import { LitElement, html, customElement, eventOptions } from "lit-element";

546

547

@customElement("event-element")

548

class EventElement extends LitElement {

549

@eventOptions({ passive: true })

550

private _handleScroll(e: Event) {

551

// Passive scroll handling for better performance

552

console.log('Scroll event (passive)');

553

}

554

555

@eventOptions({ once: true })

556

private _handleFirstClick(e: Event) {

557

// This handler will only run once

558

console.log('First click only');

559

}

560

561

@eventOptions({ capture: true })

562

private _handleCaptureClick(e: Event) {

563

// Handle in capture phase

564

console.log('Capture phase click');

565

}

566

567

render() {

568

return html`

569

<div

570

@scroll=${this._handleScroll}

571

@click=${this._handleFirstClick}

572

@click=${this._handleCaptureClick}

573

style="height: 200px; overflow-y: scroll; border: 1px solid #ccc;"

574

>

575

<div style="height: 500px; padding: 16px;">

576

<p>Scrollable content with event handling</p>

577

<button>Click me (once only + capture)</button>

578

</div>

579

</div>

580

`;

581

}

582

}

583

```