Skip to content

Lit with TypeScript

Lit has first-class TypeScript support, and most real-world Lit projects are written in TypeScript. This lesson gives an overview before the dedicated lessons on decorators, typed properties, and typed events.

Why TypeScript Is the Recommended Default

Lit's own source code is written in TypeScript, and its decorators (@customElement, @property, @state, and others) are specifically designed with TypeScript's type system in mind — property types, event detail shapes, and controller interfaces all benefit from real compile-time checking rather than relying purely on runtime behavior or documentation.

You can absolutely use Lit with plain JavaScript (as many of the earlier lessons in this course have shown), and it works well. But once a project grows past a handful of components, TypeScript's autocomplete and type checking for properties, events, and template values noticeably reduces an entire class of bugs — mistyped property names, wrong event detail shapes — before you even run the code.

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

interface Address {
  city: string;
  zip: string;
}

@customElement('user-card')
export class UserCard extends LitElement {
  @property({ type: String }) name = '';
  @property({ attribute: false }) address?: Address;

  render() {
    return html`<p>${this.name} ${this.address?.city ?? ''}</p>`;
  }
}

The Address interface gives real compile-time checking for this.address's shape — a plain JavaScript version would have no such guarantee.

Minimal tsconfig for Lit

{
  "compilerOptions": {
    "target": "ES2021",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "experimentalDecorators": true,
    "useDefineForClassFields": false,
    "strict": true
  }
}
  • experimentalDecorators: true is required for @customElement, @property, and other Lit decorators to compile.
  • useDefineForClassFields: false avoids a subtle bug where class field initialization can override Lit's own generated accessors.
  • strict: true is recommended (not required) — it catches many property-typing mistakes that would otherwise only surface at runtime.
  • Lit ships its own .d.ts type declarations, so no separate @types/lit package is needed.

Lit + TypeScript Quick Reference

Key facts for setting up a TypeScript-based Lit project.

Item Detail
Decorators import import { customElement, property, state } from 'lit/decorators.js';
Required tsconfig flag experimentalDecorators: true
Recommended tsconfig flag useDefineForClassFields: false
Type declarations Included with the lit package — no separate @types needed
JS alternative Plain static properties object, fully supported, no decorators required

Decorators vs. Plain TypeScript Without Decorators

It's entirely possible to write Lit components in TypeScript without using decorators at all, relying on the plain static properties object with type annotations on the class fields themselves. Most teams prefer decorators for their conciseness, but the plain approach remains fully valid and sometimes preferred by teams avoiding experimental TypeScript features.

class UserCard extends LitElement {
  static properties = { name: { type: String } };
  declare name: string; // type annotation without a decorator

  render() {
    return html`<p>${this.name}</p>`;
  }
}

declare tells TypeScript this field's type without generating its own initializer, since static properties already handles the runtime behavior.

How Much Type Checking Templates Actually Get

It's worth setting accurate expectations: the *inside* of an html`...` template literal is still just a string as far as TypeScript's type checker is concerned — TypeScript checks the JavaScript *expressions* inside ${...} normally, but it doesn't validate the surrounding HTML markup itself (unlike, say, JSX in React, which TypeScript understands structurally). Editor tooling (the Lit VS Code plugin) adds HTML-aware syntax highlighting and some validation on top, but this isn't the TypeScript compiler itself.

Common Mistakes

  • Forgetting experimentalDecorators: true, causing a compile error the moment @customElement or @property is used.
  • Leaving useDefineForClassFields at its default in a decorator-based project, silently breaking reactive property behavior.
  • Expecting TypeScript to catch invalid HTML markup inside an html`...` template — it only checks the JavaScript expressions inside ${...}.
  • Installing an unnecessary @types/lit package — Lit already ships its own accurate type declarations.

Key Takeaways

  • Lit has first-class TypeScript support, and decorators are the most common, recommended way to write Lit + TypeScript components.
  • experimentalDecorators: true and useDefineForClassFields: false are the two settings most Lit + TypeScript projects need.
  • Plain static properties with type annotations remains a fully valid, decorator-free alternative.
  • TypeScript checks expressions inside ${...}, not the surrounding HTML markup itself.

Pro Tip

Install the official Lit VS Code (or equivalent editor) plugin alongside your TypeScript setup — it adds HTML syntax highlighting and some template-level validation inside html`...` literals that the TypeScript compiler alone doesn't provide.