# LWC Template Anti-Patterns
This guide documents systematic errors in Lightning Web Component templates, with special focus on patterns that LLMs commonly generate incorrectly. LWC templates have strict limitations compared to frameworks like React or Vue.
> **Source**: [LLM Mistakes in Apex & LWC - Salesforce Diaries](https://salesforcediaries.com/2026/01/16/llm-mistakes-in-apex-lwc-salesforce-code-generation-rules/)
---
## Table of Contents
1. [Inline JavaScript Expressions](#1-inline-javascript-expressions)
2. [Ternary Operators in Templates](#2-ternary-operators-in-templates)
3. [Object Literals in Attributes](#3-object-literals-in-attributes)
4. [Complex Expressions](#4-complex-expressions)
5. [Event Handler Mistakes](#5-event-handler-mistakes)
6. [Iteration Anti-Patterns](#6-iteration-anti-patterns)
7. [Conditional Rendering Issues](#7-conditional-rendering-issues)
8. [Slot and Composition Errors](#8-slot-and-composition-errors)
9. [Data Binding Mistakes](#9-data-binding-mistakes)
10. [Style and Class Binding](#10-style-and-class-binding)
---
## 1. Inline JavaScript Expressions
**Critical Rule**: LWC templates do NOT support JavaScript expressions. Only property references are allowed.
### ❌ BAD: Arithmetic in Template
```html
Total: {price * quantity}
Tax: {price * 0.1}
Discount: {price - discount}
```
### ✅ GOOD: Use Getters
```javascript
// component.js
export default class PriceCalculator extends LightningElement {
price = 100;
quantity = 2;
discount = 10;
get total() {
return this.price * this.quantity;
}
get tax() {
return this.price * 0.1;
}
get discountedPrice() {
return this.price - this.discount;
}
}
```
```html
Total: {total}
Tax: {tax}
Discount: {discountedPrice}
```
### ❌ BAD: String Concatenation in Template
```html
Hello, {firstName + ' ' + lastName}!
View Account
```
### ✅ GOOD: Computed Properties
```javascript
// component.js
export default class Greeting extends LightningElement {
firstName = 'John';
lastName = 'Doe';
accountId = '001xx000003DGbY';
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
get accountUrl() {
return `/account/${this.accountId}`;
}
}
```
```html
Hello, {fullName}!
View Account
```
---
## 2. Ternary Operators in Templates
**Critical Rule**: Ternary operators (`condition ? a : b`) are NOT allowed in LWC templates.
### ❌ BAD: Ternary in Template
```html
Status
{count > 0 ? count : 'None'}
```
### ✅ GOOD: Use Getters for Conditional Values
```javascript
// component.js
export default class StatusDisplay extends LightningElement {
isActive = true;
count = 0;
isLoading = false;
get statusClass() {
return this.isActive ? 'active' : 'inactive';
}
get displayCount() {
return this.count > 0 ? this.count : 'None';
}
get isButtonDisabled() {
return this.isLoading;
}
}
```
```html
Status
{displayCount}
```
### ✅ GOOD: Use if:true/if:false for Conditional Rendering
```html
Active
Inactive
{count}
None
```
```javascript
// component.js
get hasCount() {
return this.count > 0;
}
```
---
## 3. Object Literals in Attributes
**Critical Rule**: Object literals (`{}`) cannot be passed directly as attribute values.
### ❌ BAD: Inline Object Literals
```html
```
### ✅ GOOD: Define Objects in JavaScript
```javascript
// component.js
export default class ParentComponent extends LightningElement {
config = {
showHeader: true,
theme: 'dark'
};
columns = [
{ label: 'Name', fieldName: 'name' },
{ label: 'Email', fieldName: 'email' }
];
records = [];
}
```
```html
```
### ❌ BAD: Inline Array Literals
```html
```
### ✅ GOOD: Define Arrays in JavaScript
```javascript
// component.js
export default class ColorPicker extends LightningElement {
colorOptions = [
{ label: 'Red', value: 'red' },
{ label: 'Green', value: 'green' },
{ label: 'Blue', value: 'blue' }
];
}
```
```html
```
---
## 4. Complex Expressions
**Critical Rule**: No method calls, comparisons, or logical operators in templates.
### ❌ BAD: Method Calls in Template
```html
{name.toUpperCase()}
{items.length}
{formatDate(createdDate)}
{JSON.stringify(data)}
```
### ✅ GOOD: Use Getters for Transformations
```javascript
// component.js
export default class DataDisplay extends LightningElement {
name = 'john doe';
items = ['a', 'b', 'c'];
createdDate = new Date();
data = { key: 'value' };
get upperName() {
return this.name.toUpperCase();
}
get itemCount() {
return this.items.length;
}
get formattedDate() {
return new Intl.DateTimeFormat('en-US').format(this.createdDate);
}
get dataJson() {
return JSON.stringify(this.data);
}
}
```
```html
{upperName}
{itemCount}
{formattedDate}
{dataJson}
```
### ❌ BAD: Comparisons in Template
```html
5}>
Many items
Active
```
### ✅ GOOD: Getter-Based Comparisons
```javascript
// component.js
get hasManyItems() {
return this.count > 5;
}
get isActive() {
return this.status === 'active';
}
```
```html
Many items
Active
```
### ❌ BAD: Logical Operators in Template
```html
Content
```
### ✅ GOOD: Computed Boolean Properties
```javascript
// component.js
get canDelete() {
return this.isAdmin && this.hasPermission;
}
get isNotLoading() {
return !this.isLoading;
}
```
```html
Content
```
---
## 5. Event Handler Mistakes
### ❌ BAD: Inline Event Handlers with Arguments
```html
this.handleChange(e.target.value)}>
```
### ✅ GOOD: Handler Functions with Data Attributes
```html
```
```javascript
// component.js
handleClick(event) {
const itemId = event.target.dataset.id;
// or use event.currentTarget.dataset.id for delegated events
console.log('Clicked item:', itemId);
}
handleChange(event) {
const value = event.target.value;
this.inputValue = value;
}
```
### ❌ BAD: Event Binding with bind()
```html
```
### ✅ GOOD: Use Data Attributes for Context
```html
```
```javascript
// component.js
handleItemClick(event) {
const { id, name, index } = event.currentTarget.dataset;
// dataset values are always strings
const indexNum = parseInt(index, 10);
}
```
---
## 6. Iteration Anti-Patterns
### ❌ BAD: Missing Key in Iteration
```html
{item.name}
```
### ✅ GOOD: Always Include Key
```html
{item.name}
```
### ❌ BAD: Using Index as Key
```html
{item.name}
```
### ✅ GOOD: Use Unique Identifier as Key
```javascript
// If items don't have unique IDs, generate them
connectedCallback() {
this.items = this.rawItems.map((item, index) => ({
...item,
uniqueKey: `item-${item.name}-${index}`
}));
}
```
```html
{item.name}
```
### ❌ BAD: Nested Iteration Without Proper Keys
```html
{category.name}
{item.name}
```
### ✅ GOOD: Compound Keys for Nested Iteration
```javascript
// component.js
get processedCategories() {
return this.categories.map(category => ({
...category,
items: category.items.map(item => ({
...item,
compositeKey: `${category.id}-${item.id}`
}))
}));
}
```
```html
{category.name}
{item.name}
```
---
## 7. Conditional Rendering Issues
### ❌ BAD: if:true on Non-Boolean Values
```html
Shown even for 'false' string!
Count: {count}
```
### ✅ GOOD: Explicit Boolean Conversion
```javascript
// component.js
get hasStringValue() {
return Boolean(this.stringValue) && this.stringValue !== 'false';
}
get hasCount() {
return this.count !== null && this.count !== undefined && this.count !== 0;
}
```
```html
Has value
Count: {count}
```
### ❌ BAD: Multiple Conditions Without Else
```html
Error occurred
Data loaded
```
### ✅ GOOD: Use a State Getter
```javascript
// component.js
get viewState() {
if (this.isLoading) return 'loading';
if (this.error) return 'error';
if (this.data) return 'success';
return 'empty';
}
get isLoadingState() { return this.viewState === 'loading'; }
get isErrorState() { return this.viewState === 'error'; }
get isSuccessState() { return this.viewState === 'success'; }
get isEmptyState() { return this.viewState === 'empty'; }
```
```html
```
---
## 8. Slot and Composition Errors
### ❌ BAD: Named Slot with Wrong Syntax
```html
Header
Body Content
```
### ✅ GOOD: LWC Slot Syntax
```html
Card Header
Card body content
```
```html
```
---
## 9. Data Binding Mistakes
### ❌ BAD: Two-Way Binding Syntax
```html
```
### ✅ GOOD: One-Way Binding with Event Handler
```html
```
```javascript
// component.js
name = '';
handleNameChange(event) {
this.name = event.detail.value; // lightning-input uses detail.value
}
handleInputChange(event) {
this.name = event.target.value; // standard input uses target.value
}
```
### ❌ BAD: Direct Property Mutation in Template
```html
```
### ✅ GOOD: Mutate in Handler
```javascript
// component.js
count = 0;
handleIncrement() {
this.count++;
}
```
```html
```
---
## 10. Style and Class Binding
### ❌ BAD: Dynamic Styles in Template
```html
Content
Content
```
### ✅ GOOD: CSS Custom Properties (Recommended)
```javascript
// component.js
@api textColor = 'blue';
@api fontSize = 14;
renderedCallback() {
this.template.host.style.setProperty('--text-color', this.textColor);
this.template.host.style.setProperty('--font-size', `${this.fontSize}px`);
}
```
```css
/* component.css */
.dynamic-text {
color: var(--text-color, black);
font-size: var(--font-size, 14px);
}
```
```html
Content
```
### ✅ GOOD: Computed Style String (When Necessary)
```javascript
// component.js
get dynamicStyle() {
return `color: ${this.textColor}; font-size: ${this.fontSize}px;`;
}
```
```html
Content
```
### ❌ BAD: Dynamic Class with Expression
```html
Content
Content
```
### ✅ GOOD: Computed Class String
```javascript
// component.js
get containerClass() {
return `base ${this.isActive ? 'active' : ''} ${this.isHighlighted ? 'highlighted' : ''}`.trim();
}
```
```html
Content
```
---
## Quick Reference: Template Rules
| What You Want | Wrong (Other Frameworks) | Right (LWC) |
|---------------|-------------------------|-------------|
| Arithmetic | `{a + b}` | Getter: `get sum() { return a + b; }` |
| String concat | `{a + ' ' + b}` | Getter with template literal |
| Ternary | `{x ? a : b}` | Getter or if:true/if:false |
| Method call | `{items.length}` | Getter: `get count() { return items.length; }` |
| Comparison | `if:true={x > 5}` | Getter: `get isBig() { return x > 5; }` |
| Logical AND | `if:true={a && b}` | Getter: `get both() { return a && b; }` |
| Negation | `if:true={!x}` | `if:false={x}` or getter |
| Object literal | `config={{ key: val }}` | Class property |
| Event args | `onclick={fn(x)}` | Use data attributes |
| Two-way bind | `value={name}` auto-update | `value={name}` + `onchange` |
---
## Validation Checklist
Before deploying LWC templates, verify:
- [ ] No arithmetic operations (`+`, `-`, `*`, `/`)
- [ ] No ternary operators (`? :`)
- [ ] No object/array literals (`{}`, `[]`)
- [ ] No method calls (`.length`, `.toUpperCase()`)
- [ ] No comparisons (`>`, `<`, `===`, `!==`)
- [ ] No logical operators (`&&`, `||`, `!`)
- [ ] All iterations have unique `key` attributes
- [ ] Event handlers don't have inline arguments
- [ ] Dynamic styles use CSS custom properties or computed strings
---
## Reference
- **LWC Best Practices**: See `references/lwc-best-practices.md`
- **Component Patterns**: See `references/component-patterns.md`
- **Source**: [Salesforce Diaries - LLM Mistakes](https://salesforcediaries.com/2026/01/16/llm-mistakes-in-apex-lwc-salesforce-code-generation-rules/)