Render Lists in LWC – for:each & iterator Directives

Render Lists in LWC – for:each & iterator Directives

Render Lists in LWC

for:each & iterator Directives — Complete Guide with Examples

Learn how to use for:each and iterator directives in Lightning Web Components to efficiently render dynamic lists — and understand the key differences, rules, and best practices for each approach.

1 Introduction

Rendering lists is one of the most common tasks in any UI framework. In Lightning Web Components (LWC), you have two built-in directives for iterating over arrays in your HTML template:

for:each — The standard directive for looping over an array and rendering a block of HTML for each item.
iterator — An advanced directive that provides extra metadata (first, last, index, value) for each item — useful when you need to style the first or last item differently.

⚠️ Important: Both directives require a key attribute on the first child element inside the loop. Without key, LWC will throw an error. The key must be unique and stable for each item.

Both directives are applied on a <template> tag and do not add any extra DOM element to the rendered page.

2 for:each — Syntax & Basic Example

for:each iterates over an array. You must also declare a loop variable using for:item, and optionally the index using for:index.

📌 Syntax: <template for:each={arrayProperty} for:item="itemVar" for:index="indexVar">
HTML — fruitList.html
<template>
    <lightning-card title="Fruit List" icon-name="utility:list">
        <ul class="slds-p-around_medium">

            <template for:each={fruits} for:item="fruit">
                <li key={fruit.id}>
                    {fruit.name}
                </li>
            </template>

        </ul>
    </lightning-card>
</template>
JavaScript — fruitList.js
import { LightningElement } from 'lwc';

export default class FruitList extends LightningElement {
    fruits = [
        { id: 1, name: '🍎 Apple'  },
        { id: 2, name: '🍌 Banana' },
        { id: 3, name: '🍇 Grapes' },
        { id: 4, name: '🍊 Orange' },
        { id: 5, name: '🍓 Strawberry' }
    ];
}

for:each={fruits} binds the array from the JS class.
for:item="fruit" declares the loop variable — use it as {fruit.id}, {fruit.name} etc.
key={fruit.id} on the <li> (the first child) tells LWC how to track elements efficiently.

3 for:each with for:index — Using the Index

You can also access the current index of each item using the for:index attribute. This is useful for displaying row numbers or conditional logic based on position.

HTML — indexedList.html
<template>
    <lightning-card title="Team Members" icon-name="utility:people">
        <div class="slds-p-around_medium">

            <template for:each={teamMembers} for:item="member" for:index="idx">
                <div key={member.id} class="slds-m-bottom_small">
                    <b>{idx}.</b> {member.name} — {member.role}
                </div>
            </template>

        </div>
    </lightning-card>
</template>
JavaScript — indexedList.js
import { LightningElement } from 'lwc';

export default class IndexedList extends LightningElement {
    teamMembers = [
        { id: 'u1', name: 'Alice Johnson', role: 'Developer'    },
        { id: 'u2', name: 'Bob Smith',     role: 'Designer'     },
        { id: 'u3', name: 'Carol White',   role: 'Product Manager' },
        { id: 'u4', name: 'David Lee',     role: 'QA Engineer'  }
    ];
}

for:index="idx" gives the zero-based position of each item in the array.
• The index is a read-only value inside the template — you cannot modify the array through it.
• Use string IDs (like 'u1') for the key — they are more stable than numeric array indexes.

⚠️ Do not use the loop index as the key (e.g., key={idx}). If the array is reordered or filtered, LWC will incorrectly reuse DOM nodes. Always use a unique, stable identifier from the data itself.

4 iterator — Syntax & Basic Example

The iterator directive provides richer metadata for each loop iteration. Instead of a plain item variable, each iteration exposes an object with four properties:

value — The current item from the array.
index — The zero-based position.
firsttrue if this is the first item.
lasttrue if this is the last item.

📌 Syntax: <template iterator:iteratorVar={arrayProperty}>
HTML — iteratorDemo.html
<template>
    <lightning-card title="Product List" icon-name="utility:product_consumed">
        <ul class="slds-p-around_medium">

            <template iterator:prod={products}>
                <li key={prod.value.id}
                    class={prod.value.itemClass}>

                    <template lwc:if={prod.first}>
                        <span class="slds-badge">⭐ Top Pick</span>
                    </template>

                    {prod.value.name} — ${prod.value.price}

                    <template lwc:if={prod.last}>
                        <span class="slds-badge slds-badge_inverse">End of List</span>
                    </template>

                </li>
            </template>

        </ul>
    </lightning-card>
</template>
JavaScript — iteratorDemo.js
import { LightningElement } from 'lwc';

export default class IteratorDemo extends LightningElement {
    products = [
        { id: 'p1', name: 'Laptop Pro',   price: 1299 },
        { id: 'p2', name: 'Wireless Mouse', price: 49  },
        { id: 'p3', name: 'USB-C Hub',    price: 79   },
        { id: 'p4', name: 'Monitor 4K',   price: 599  }
    ];
}

iterator:prod={products}prod is the iterator variable for each iteration.
• Access item data via {prod.value.name} — the actual object is under .value.
{prod.first} is true only on the first item — perfect for adding a "Top Pick" badge.
{prod.last} is true only on the last item — useful for "End of List" markers or removing borders.

5 Practical Example — Dynamic Table with iterator

A common real-world use case: render a table of records where the first row gets a highlighted style and the last row has no bottom border. This is trivial with iterator.

HTML — employeeTable.html
<template>
    <lightning-card title="Employee Directory" icon-name="utility:groups">
        <div class="slds-p-around_medium">

            <table class="slds-table slds-table_bordered slds-table_cell-buffer">
                <thead>
                    <tr class="slds-line-height_reset">
                        <th scope="col">Name</th>
                        <th scope="col">Department</th>
                        <th scope="col">Salary</th>
                    </tr>
                </thead>
                <tbody>

                    <template iterator:emp={employees}>
                        <tr key={emp.value.id}
                            class={emp.value.rowClass}>
                            <td>
                                <template lwc:if={emp.first}>
                                    ⭐
                                </template>
                                {emp.value.name}
                            </td>
                            <td>{emp.value.department}</td>
                            <td>${emp.value.salary}</td>
                        </tr>
                    </template>

                </tbody>
            </table>

        </div>
    </lightning-card>
</template>
JavaScript — employeeTable.js
import { LightningElement } from 'lwc';

export default class EmployeeTable extends LightningElement {
    employees = [
        { id: 'e1', name: 'Sarah Connor',  department: 'Engineering', salary: 120000 },
        { id: 'e2', name: 'John Doe',      department: 'Marketing',   salary: 85000  },
        { id: 'e3', name: 'Jane Smith',    department: 'Sales',       salary: 95000  },
        { id: 'e4', name: 'Mike Johnson',  department: 'Finance',     salary: 105000 }
    ];
}

• The iterator automatically identifies emp.first → the star icon is conditionally shown only on the first employee row.
key is placed on the <tr> element — the first child of the iterator template.
lwc:if and iterator work together seamlessly — no extra JavaScript needed for first/last detection.

6 Nested for:each — Lists Within Lists

You can nest for:each directives to render nested data structures, like a list of categories each containing sub-items.

HTML — nestedList.html
<template>
    <lightning-card title="Menu Categories" icon-name="utility:rows">
        <div class="slds-p-around_medium">

            <template for:each={menuCategories} for:item="category">
                <div key={category.id} class="slds-m-bottom_medium">

                    <h3 style="font-weight:bold; color:#032D60;">
                        {category.name}
                    </h3>

                    <ul class="slds-m-top_x-small">
                        <template for:each={category.items} for:item="item">
                            <li key={item.id} class="slds-m-left_medium">
                                {item.label}
                            </li>
                        </template>
                    </ul>

                </div>
            </template>

        </div>
    </lightning-card>
</template>
JavaScript — nestedList.js
import { LightningElement } from 'lwc';

export default class NestedList extends LightningElement {
    menuCategories = [
        {
            id: 'c1',
            name: '🍔 Mains',
            items: [
                { id: 'i1', label: 'Burger'    },
                { id: 'i2', label: 'Pasta'     },
                { id: 'i3', label: 'Pizza'     }
            ]
        },
        {
            id: 'c2',
            name: '🍰 Desserts',
            items: [
                { id: 'i4', label: 'Ice Cream' },
                { id: 'i5', label: 'Cake'      }
            ]
        },
        {
            id: 'c3',
            name: '🥤 Drinks',
            items: [
                { id: 'i6', label: 'Juice'     },
                { id: 'i7', label: 'Lemonade'  }
            ]
        }
    ];
}

• Each level of nesting requires its own unique key on the first child element of that loop.
• The outer loop uses key={category.id} on the <div>.
• The inner loop uses key={item.id} on each <li>.
• Ensure all IDs in the entire nested structure are globally unique to avoid key collisions.

7 for:each vs iterator — Comparison

Aspect for:each iterator
Syntax for:each={arr} for:item="x" iterator:x={arr}
Access item value {x.property} {x.value.property}
Access index for:index="idx"{idx} {x.index} (built-in)
First item detection ❌ Not available {x.first}
Last item detection ❌ Not available {x.last}
Use case Standard list rendering When first/last styling needed
Complexity Simpler syntax Slightly more verbose
key requirement ✅ Required on first child ✅ Required on first child

8 Key Rules & Best Practices

Rule Detail
key is mandatory Every loop must have a key attribute on the first child of the template. LWC uses it for efficient DOM reconciliation.
key must be unique & stable Use a record ID or a unique field from your data. Never use the array index as the key — it causes bugs when items are reordered or deleted.
key must be a string The key value must be a primitive (string or number). Avoid using objects as keys.
Only on <template> tags Both for:each and iterator must be placed on <template> tags, not on regular HTML elements.
Use getter for computed arrays If you need to filter, sort, or transform the array before rendering, use a JavaScript get function. Never mutate the original tracked array directly.
iterator for first/last only Only use iterator when you actually need .first or .last. For everything else, prefer the simpler for:each syntax.
No expressions in for:each The array property must be referenced as {myArray} — you cannot write inline expressions like {myArray.filter(...)} in the template. Use a getter instead.

9 Quick Reference

for:each (basic):

<template for:each={myArray} for:item="item">
    <div key={item.id}>{item.name}</div>
</template>

for:each with index:

<template for:each={myArray} for:item="item" for:index="idx">
    <div key={item.id}>{idx}. {item.name}</div>
</template>

iterator (with first / last):

<template iterator:it={myArray}>
    <div key={it.value.id}>

        <template lwc:if={it.first}>
            <span>First Item!</span>
        </template>

        {it.value.name}   <!-- Index: {it.index} -->

        <template lwc:if={it.last}>
            <span>Last Item!</span>
        </template>

    </div>
</template>

Getter for filtered/sorted list:

// In your JS class — compute the list in a getter
get sortedEmployees() {
    return [...this.employees].sort((a, b) => a.name.localeCompare(b.name));
}
<!-- In your template -->
<template for:each={sortedEmployees} for:item="emp">
    <div key={emp.id}>{emp.name}</div>
</template>
📝 Remember: Use for:each for standard lists. Use iterator only when you need .first / .last metadata. Always provide a unique, stable key on the first child — never use the array index as the key.

Popular posts from this blog

Handling Errors in LWC

Hello World using LWC

Lightning Card in LWC