08 - Control Flow
Svelte uses template blocks for conditional rendering, loops, async data, and re-rendering. These are NOT runes — they're part of Svelte's template syntax and work the same in Svelte 4 and 5.
{#if} — Conditional Rendering
Show or hide elements based on a condition.
<!-- src/lib/components/UserStatus.svelte -->
<script lang="ts">
let { loggedIn, isAdmin } = $props();
</script>
{#if loggedIn && isAdmin}
<p>Welcome, admin!</p>
{:else if loggedIn}
<p>Welcome back!</p>
{:else}
<p>Please log in.</p>
{/if}{:else if}and{:else}are optional- The block completely removes elements from the DOM (not just hiding with CSS)
{#each} — Loops
Render a list of items.
Basic Loop
<!-- src/lib/components/CourseList.svelte -->
<script lang="ts">
let courses = $state(['Svelte', 'TypeScript', 'SvelteKit']);
</script>
<ul>
{#each courses as course}
<li>{course}</li>
{/each}
</ul>With Index
{#each courses as course, index}
<li>{index + 1}. {course}</li>
{/each}With Key (important for dynamic lists)
When items can be added, removed, or reordered, Svelte needs a unique key to track which DOM element belongs to which item. Without a key, Svelte updates by position — which causes bugs with animations, component state, and reordering.
<script lang="ts">
type Todo = { id: number; text: string };
let todos = $state<Todo[]>([
{ id: 1, text: 'Learn Svelte' },
{ id: 2, text: 'Build LMS' }
]);
</script>
<!-- (item.id) is the key — must be unique per item -->
{#each todos as todo (todo.id)}
<li>{todo.text}</li>
{/each}When to use a key:
- List items can be reordered, added, or removed → always use a key
- Static list that never changes → key is optional
Destructuring
{#each todos as { id, text } (id)}
<li>{text}</li>
{/each}Empty List with {:else}
{#each todos as todo (todo.id)}
<li>{todo.text}</li>
{:else}
<p>No items yet.</p>
{/each}The {:else} block renders when the array is empty.
{#await} — Async Data
Handle promises directly in the template — shows loading, success, and error states.
Full Pattern (loading → success → error)
<!-- src/lib/components/CourseLoader.svelte -->
<script lang="ts">
type Course = { title: string; lessons: number };
async function fetchCourse(): Promise<Course> {
const res = await fetch('/api/course');
if (!res.ok) throw new Error('Failed to load');
return res.json();
}
let coursePromise = fetchCourse();
</script>
{#await coursePromise}
<p>Loading course...</p>
{:then course}
<h2>{course.title}</h2>
<p>{course.lessons} lessons</p>
{:catch error}
<p>Error: {error.message}</p>
{/await}Skip Loading State
If you don't need a loading indicator:
{#await coursePromise then course}
<h2>{course.title}</h2>
{/await}Skip Error Handling
{#await coursePromise}
<p>Loading...</p>
{:then course}
<h2>{course.title}</h2>
{/await}Note: In SvelteKit, you'll usually load data in
+page.tsload functions instead of using{#await}in templates.{#await}is more useful for client-side fetches triggered by user actions.
{#key} — Force Re-render
Destroys and recreates its contents when the expression changes. Useful for resetting component state or re-triggering transitions.
<!-- src/lib/components/LessonView.svelte -->
<script lang="ts">
let { lessonId } = $props();
</script>
<!-- When lessonId changes, the entire block is destroyed and recreated -->
{#key lessonId}
<LessonContent id={lessonId} />
{/key}Without {#key}, Svelte would reuse the same component instance and just update props. With {#key}, it creates a fresh instance — resetting all internal state.
When to use:
- Reset a component's internal
$statewhen a prop changes - Re-trigger intro/outro animations on data change
- Force a fresh fetch inside a component
Key Takeaways
{#if}— conditional rendering, removes elements from DOM entirely{#each}— loops, use(key)for dynamic lists to avoid bugs{#each ... {:else}}— handles empty arrays{#await}— loading/success/error for promises (prefer SvelteKit load functions for page data){#key}— destroy and recreate content when a value changes- These are template blocks, not runes — same syntax since Svelte 3/4