Looping with {#each}
Rendering lists from arrays, with keyed items so Svelte can update the DOM efficiently.
2 min read
{#each} renders a block of markup once per item in an array — the loop counterpart to {#if}.
<script>
let fruits = $state(['apple', 'banana', 'cherry']);
</script>
<ul>
{#each fruits as fruit}
<li>{fruit}</li>
{/each}
</ul>You get the index too, as a second variable, exactly like Array.prototype.forEach:
{#each fruits as fruit, i}
<li>{i + 1}. {fruit}</li>
{/each}Keying each item
By default, if fruits changes, Svelte compares the new array to the old one and tries to reuse existing DOM nodes based on position. That's usually fine — until items get reordered, inserted in the middle, or removed from anywhere but the end, at which point position-based matching means the wrong DOM nodes get reused for the wrong items (visible as stale input focus, broken transitions, or components appearing to update in place with the wrong data).
The fix is a key, given in parentheses after the loop:
<script>
let todos = $state([
{ id: 1, text: 'Buy milk' },
{ id: 2, text: 'Walk the dog' },
]);
</script>
{#each todos as todo (todo.id)}
<li>{todo.text}</li>
{/each}With (todo.id), Svelte tracks each list item by its identity, not its position. Reorder todos, and the existing DOM nodes move along with their data instead of being reused in place with new content swapped in — any per-item state (like an input mid-edit) travels with the right item.
Use a stable, unique key whenever list items can be reordered, inserted, or removed from the middle — which, for most real data (todos, search results, rows from an API), is nearly always. An array index is not a safe key for this, since it changes whenever items shift position.
Nested loops and empty states
<script>
let categories = $state([
{ name: 'Fruit', items: ['apple', 'banana'] },
{ name: 'Veg', items: [] },
]);
</script>
{#each categories as category (category.name)}
<h3>{category.name}</h3>
{#if category.items.length === 0}
<p>Nothing here yet.</p>
{:else}
<ul>
{#each category.items as item}
<li>{item}</li>
{/each}
</ul>
{/if}
{/each}{#each} and {#if} compose freely — nesting them is how you handle the common case of "a list that might be empty," rather than reaching for a special syntax.
An {:else} for the whole loop
{#each} also has its own {:else}, which renders once if the array is empty — a shorter alternative to the manual {#if length === 0} check above:
{#each todos as todo (todo.id)}
<li>{todo.text}</li>
{:else}
<p>No todos yet — add one above.</p>
{/each}