Skip to content

Lit Do's and Don'ts

This lesson pairs common Lit anti-patterns with their recommended fix, side by side, across properties, templates, styling, and events.

Reading This Lesson

Each topic below shows a 'don't' pattern immediately followed by the recommended 'do' alternative, so you can quickly recognize a pattern you may already be using and see the concrete fix, rather than re-reading full explanations for concepts already covered in earlier lessons.

// Don't: mutate an array property in place
this.items.push(newItem);

// Do: replace the reference so Lit's change detection notices
this.items = [...this.items, newItem];

This is the single most common Lit anti-pattern — in-place mutation silently fails to trigger a re-render.

Quick-Scan Do/Don't List

Don't mutate objects/arrays in place -> Do replace the reference
Don't fetch data in render() -> Do fetch in connectedCallback()
Don't use array index as a repeat() key -> Do use a stable id
Don't hardcode colors in every component -> Do use CSS custom properties
Don't skip cleanup in disconnectedCallback -> Do pair every setup with teardown
  • Use this list as a fast self-check before opening a pull request.
  • Each line maps to a full explanation in an earlier lesson if you need the underlying reasoning.
  • None of these are Lit-specific quirks — most map to general reactive-UI and web platform best practices.
  • Treat any 'don't' you recognize in your own code as a prioritized, low-risk refactor target.

Do's and Don'ts Reference Table

A scannable table pairing each anti-pattern with its fix.

Don't Do Instead
Mutate arrays/objects in place Assign a new reference
Fetch data inside render() Fetch in connectedCallback(), store in @state()
Use the array index as a repeat() key Use a stable id from the data itself
Hardcode colors/spacing per component Read from shared CSS custom properties
Skip disconnectedCallback cleanup Pair every setup with matching teardown
Mutate a parent's state directly from a child Dispatch a custom event and let the parent decide

Properties and State

The most common properties/state mistakes involve either bypassing change detection through mutation, or blurring the line between public and internal reactive fields.

// Don't: expose purely internal state as a public property
@property() isDropdownOpen = false;

// Do: use @state() for internal-only fields
@state() private isDropdownOpen = false;

Templates and Styling

Template and styling mistakes usually involve fighting the platform's encapsulation instead of using the intended theming mechanisms.

/* Don't: try to reach into a component's Shadow DOM from page CSS */
my-card .title { color: red; } /* has no effect — blocked by Shadow DOM */

/* Do: expose a themeable custom property from inside the component */
/* Inside my-card's static styles: */
.title { color: var(--my-card-title-color, inherit); }
/* From page CSS: */
my-card { --my-card-title-color: red; }

Events: Data Down, Notifications Up

The clearest sign of an events anti-pattern is a child component reaching up and mutating a parent's internal state directly, rather than describing what happened and letting the parent decide how to respond.

Good Example

// Do: child dispatches an event describing what happened
this.dispatchEvent(new CustomEvent('item-removed', {
  detail: { id: item.id }, bubbles: true, composed: true,
}));

Bad Example

// Don't: child reaches up and mutates the parent directly
this.closest('todo-app').todos = this.closest('todo-app').todos.filter(t => t.id !== item.id);
  • Keep data flowing down through properties and notifications flowing up through events, consistently.
  • Never assume a specific ancestor structure (like closest('todo-app')) — it breaks the component's reusability.
  • Let the receiving component decide how (and whether) to react to a dispatched event.

Common Mistakes

  • Recognizing a 'don't' pattern in existing code but not treating it as a prioritized refactor target.
  • Fixing the symptom (a component that 'doesn't update') without fixing the actual root cause (in-place mutation).
  • Applying a 'do' fix inconsistently — only in newly written components, leaving older ones with the same anti-pattern.
  • Skipping the reasoning behind a 'do' recommendation and just pattern-matching the syntax without understanding why it matters.

Key Takeaways

  • In-place mutation of objects/arrays is the most common Lit anti-pattern — always replace the reference instead.
  • Keep the line between public (@property) and internal (@state) reactive fields clear and deliberate.
  • Use CSS custom properties for theming rather than fighting Shadow DOM encapsulation from page-level CSS.
  • Events should flow up from children as notifications, never as direct parent-state mutation.

Pro Tip

Keep this do/don't table pinned somewhere your team actually looks during code review (a wiki page, a PR template) — a scannable table like this catches far more real anti-patterns in practice than a long-form best-practices essay that's read once and forgotten.