@property() accepts a configuration object with several options controlling exactly how a reactive property behaves. This lesson covers every option in detail.
The Full Property Options Object
Every option passed to @property({ ... }) (or listed per-property in static properties) controls one specific aspect of how that property interacts with attributes, change detection, or the generated accessor. Most properties only need type, but knowing every option helps you solve less common cases correctly instead of working around them.
These same options apply whether you use the TypeScript decorator syntax or the plain JavaScript static properties object — they're just two different syntaxes for configuring the exact same underlying system.
type also accepts a full custom converter function/object for values that don't fit the built-ins.
attribute: false combined with reflect: true is invalid/meaningless — reflection requires an attribute to reflect to.
noAccessor: true skips Lit's generated getter/setter, useful only if you're implementing custom accessors yourself.
state: true is exactly what the @state() decorator sets for you automatically.
Property Options Reference
Every option, its type, and what it controls.
Option
Type
Controls
type
Constructor or converter object
Attribute string <-> property value conversion
attribute
boolean | string
Whether/what attribute is observed
reflect
boolean
Property change writes back to the attribute
hasChanged
(new, old) => boolean
Custom equality check for change detection
converter
{ fromAttribute, toAttribute }
Fully custom serialization logic
state
boolean
Marks the property internal (no attribute)
noAccessor
boolean
Skips generating a reactive accessor
Custom Attribute Names
Because HTML attributes are conventionally lowercase and kebab-case, while JavaScript properties are camelCase, you'll sometimes want an attribute name that doesn't directly match the property name — for example, aligning with an existing design system's naming, or avoiding an awkward auto-lowercased name.
For value types the built-in converters don't handle well — a comma-separated list, a custom serialized format — pass a converter object with fromAttribute and toAttribute functions instead of a type constructor.
Custom converters give full control when the built-in String/Number/Boolean/Object/Array types don't fit your data shape.
Common Mistakes
Setting reflect: true alongside attribute: false, which has no effect since there's no attribute to reflect to.
Writing a hasChanged function that forgets to handle the initial undefined old value, throwing on first render.
Reaching for a full custom converter when a built-in type (like Array with JSON) would already work fine.
Using noAccessor: true without implementing your own getter/setter, silently breaking reactivity for that property.
Key Takeaways
@property() options give fine-grained control over attribute naming, reflection, change detection, and conversion.
attribute: false and reflect: true are mutually pointless together — reflection needs an attribute.
Custom converters (fromAttribute/toAttribute) handle value shapes the built-in types don't cover.
state: true is exactly what @state() configures automatically — the two are equivalent under the hood.
Pro Tip
Reach for the built-in type converters first, and only write a custom converter when you've confirmed the built-in Object/Array JSON-based conversion genuinely doesn't fit — it covers more cases than developers coming from other frameworks often expect.
You now have a complete reference for property options. Next, look closer at attribute reflection specifically.