Svelte Component Composition
Build flexible components in Svelte 5 using snippets, {@render}, typed children props, and named content areas
When to Use
- You are building a layout component (Card, Modal, Dialog) that renders caller-provided content
- You need multiple named content areas in a single component (header, body, footer slots)
- You are migrating Svelte 4
<slot>patterns to Svelte 5 snippets - You want to pass render functions as props for renderless/headless component patterns
Instructions
Svelte 5 snippets — defining reusable template blocks:
- Define snippets with
{#snippet name(params)}and render them with{@render name()}:
{#snippet greeting(name: string)}
<p>Hello, <strong>{name}</strong>!</p>
{/snippet}
{@render greeting('Alice')}
{@render greeting('Bob')}
Children prop — default content slot:
- Accept default content from a parent using the
childrenprop (typed asSnippet):
<!-- Card.svelte -->
<script lang="ts">
import type { Snippet } from 'svelte'
let { children }: { children: Snippet } = $props()
</script>
<div class="card">
{@render children()}
</div>
<!-- Usage -->
<Card>
<p>This content is passed as children.</p>
</Card>
- Make children optional with a fallback:
<script lang="ts">
import type { Snippet } from 'svelte'
let { children }: { children?: Snippet } = $props()
</script>
{#if children}
{@render children()}
{:else}
<p>No content provided.</p>
{/if}
Named snippets — multiple content areas:
- Accept named snippet props for multiple content areas:
<!-- Dialog.svelte -->
<script lang="ts">
import type { Snippet } from 'svelte'
let {
title,
children,
footer
}: {
title: Snippet
children: Snippet
footer?: Snippet
} = $props()
</script>
<dialog>
<header>{@render title()}</header>
<main>{@render children()}</main>
{#if footer}
<footer>{@render footer()}</footer>
{/if}
</dialog>
<!-- Usage -->
<Dialog>
{#snippet title()}
<h2>Confirm Delete</h2>
{/snippet}
<p>Are you sure you want to delete this item?</p>
{#snippet footer()}
<button
<button
{/snippet}
</Dialog>
Snippets with parameters — render prop pattern:
- Pass parameters through snippets to implement render prop / headless patterns:
<!-- List.svelte -->
<script lang="ts">
import type { Snippet } from 'svelte'
let {
items,
renderItem
}: {
items: Item[]
renderItem: Snippet<[Item, number]>
} = $props()
</script>
<ul>
{#each items as item, index}
<li>{@render renderItem(item, index)}</li>
{/each}
</ul>
<!-- Usage -->
<List {items}>
{#snippet renderItem(item, index)}
<span>{index + 1}. {item.name}</span>
{/snippet}
</List>
Svelte 4 migration — slots to snippets:
- Replace
<slot>with{@render children()}and named slots with named snippet props:
| Svelte 4 | Svelte 5 |
|---|---|
<slot /> |
{@render children()} |
<slot name="header" /> |
{@render header()} |
<slot {item} /> |
{@render renderItem(item)} |
<svelte:fragment slot="header"> |
{#snippet header()} |
Passing snippets programmatically:
- Use
renderSnippetfromsveltewhen you need to render a snippet in a non-template context (e.g., passing to a library):
import { renderSnippet } from 'svelte';
const rendered = renderSnippet(mySnippet, args);
Details
Snippets vs. components:
Snippets are template fragments — they share the reactive scope of their defining component. Components are isolated scopes with their own lifecycle. Use snippets when you need to inject templating into a parent component; use child components when you need encapsulation.
Snippet type signatures:
import type { Snippet } from 'svelte';
Snippet; // no params
Snippet<[]>; // explicit no params
Snippet<[string]>; // one string param
Snippet<[Item, number]>; // two params
{@render} with null safety:
{@render} will throw if passed undefined. Always check optionality:
{#if footer}
{@render footer()}
{/if}
Or use the ?? pattern with a fallback snippet.
Svelte 4 backward compatibility:
<slot> still works in Svelte 5 but is deprecated. The children snippet prop is backward-compatible — components using {@render children()} can receive content from both Svelte 5 snippet syntax and legacy <slot> fallback in child components.
Composing deeply nested layouts:
For deeply nested layouts (e.g., shell > sidebar > content), pass snippet props down or use the context API to avoid prop threading. The context API allows any descendant to read values without passing through intermediate components.
Source
https://svelte.dev/docs/svelte/snippet
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.