This lesson focuses specifically on getting accurate, useful TypeScript types for reactive properties, covering common patterns like union types, optional fields, and object-shaped properties.
Typing @property() Fields
With the decorator syntax, TypeScript infers a property's type directly from its declared type annotation and default value, exactly like any other class field — @property() doesn't need you to repeat the type inside the decorator's options object (though type: Number/type: Boolean etc. inside the options object still control the runtime attribute conversion, which is a separate concern from the TypeScript type itself).
For properties with a restricted set of valid values — a variant prop that's only ever 'primary' | 'secondary' | 'danger' — union string literal types give you compile-time checking that a plain String type annotation alone wouldn't provide.
variant's TypeScript type (Variant) and its runtime attribute conversion (the default String behavior) are two independent, complementary layers of correctness.
onSelect?: (id: string) => void; with attribute: false
Plain internal boolean flag
@state() private isOpen = false;
Typing Fields When Using Plain static properties
If your project uses the plain static properties object instead of decorators, TypeScript doesn't automatically know those fields are reactive properties with real types. Use the declare keyword to add a type annotation for each field without generating a conflicting class field initializer.
declare tells TypeScript the field's type exists and will be assigned elsewhere (here, in the constructor), without emitting its own field initializer that could conflict with static properties.
Exporting Property Types for Consumers
If other TypeScript code in your project (or a published package) constructs or type-checks against your component's properties, export any shared types (like the Variant union above) so consumers get the same compile-time checking your own component's internals benefit from.
// action-button.ts
export type Variant = 'primary' | 'secondary' | 'danger';
// consumer.ts
import type { Variant } from './action-button.js';
const myVariant: Variant = 'primary';
Common Mistakes
Using a plain string type for a property that really only accepts a small fixed set of values, missing out on compile-time checking a union type would provide.
Forgetting declare when adding TypeScript types to fields defined through the plain static properties object, causing field initialization conflicts.
Not exporting shared prop types, forcing consumers to redefine the same union/interface themselves.
Confusing a property's type: runtime option (attribute conversion) with its TypeScript compile-time type — they serve different purposes and are configured independently.
Key Takeaways
Decorator-based properties get their TypeScript type from the field's own type annotation and default value.
Union string literal types are an easy, effective way to type properties with a small fixed set of valid values.
declare is needed to add TypeScript types to fields defined through the plain static properties object.
Export shared property types so other TypeScript code consuming your component benefits from the same type safety.
Pro Tip
As soon as a string property's valid values are countable (a size, variant, or status prop), convert it to a union string literal type immediately — it's one of the highest-value, lowest-effort type safety improvements available in a Lit + TypeScript component.
You now know how to type reactive properties well. Next, learn how to type custom events for consumers.