{#each}
{#each} iterates arrays. Always provide a key (the second argument to as) so Svelte can correctly identify items across reorders, inserts, deletes — otherwise inputs lose state on shuffle.
Keys, index, empty fallback, snippets
EXAMPLE
<script>
let todos = $state([
{ id: 1, text: 'Learn Svelte', done: false },
{ id: 2, text: 'Ship MVP', done: false },
{ id: 3, text: 'Sleep', done: false },
]);
function add() {
todos.push({ id: Date.now(), text: 'New', done: false });
}
function remove(id) {
todos = todos.filter(t => t.id !== id);
}
</script>
<button onclick={add}>Add</button>
<button onclick={() => todos.reverse()}>Reverse</button>
<!-- 1) Iterate WITH A KEY (always) -->
<ul>
{#each todos as t (t.id)}
<li class:done={t.done}>
<input type="checkbox" bind:checked={t.done}>
<input bind:value={t.text}>
<button onclick={() => remove(t.id)}>×</button>
</li>
{:else}
<li class="empty">No todos yet.</li>
{/each}
</ul>
<!-- 2) Index -->
<ol>
{#each todos as t, i (t.id)}
<li>#{i + 1}. {t.text}</li>
{/each}
</ol>
<!-- 3) Destructure items -->
{#each users as { id, name, role } (id)}
<li>{name} ({role})</li>
{/each}
<!-- 4) Iterate a Map / Set (Svelte 5 — both are reactive) -->
<script>
let scores = $state(new Map([['Ada', 92], ['Bo', 78]]));
</script>
<ul>
{#each scores as [name, score] (name)}
<li>{name}: {score}</li>
{/each}
</ul>
<!-- 5) Snippets — reusable markup blocks (Svelte 5) -->
{#snippet row(t)}
<li class:done={t.done}>
<input type="checkbox" bind:checked={t.done}>
{t.text}
</li>
{/snippet}
<ul>
{#each todos as t (t.id)}
{@render row(t)}
{/each}
</ul>
Why it matters
Without a key, Svelte reuses DOM nodes positionally. Reordering re-uses the FIRST input’s state for the new first item — subtly wrong and a top source of “input shows the wrong value” bugs.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Exercise
Iterate a list in markup.
{
items as item (item.id)}<li>{item.name}</li>{/each}
Begins with #.
Discussion
Loading…