{#snippet}
Svelte 5 snippets are reusable template fragments you can pass between components — replacing slots with a more powerful, typed model. Use for layouts, repeated patterns, configurable cells.
Define, render, pass as props
EXAMPLE
<!-- 1) Define a snippet — declares a render function -->
<script>
let items = [
{ id: 1, name: 'Ada', email: 'a@x.com' },
{ id: 2, name: 'Bo', email: 'b@x.com' },
];
</script>
{#snippet user(u)}
<div class="user">
<h3>{u.name}</h3>
<p>{u.email}</p>
</div>
{/snippet}
<!-- 2) Render a snippet with {@render} -->
{@render user(items[0])}
<!-- 3) Use in a loop -->
{#each items as item (item.id)}
{@render user(item)}
{/each}
<!-- 4) Multiple parameters -->
{#snippet card(title, body, footer)}
<article>
<h2>{title}</h2>
<p>{body}</p>
<footer>{footer}</footer>
</article>
{/snippet}
{@render card('Title', 'Some body text', 'Footer text')}
<!-- 5) Conditional rendering of snippet -->
{#if hasContent}
{@render content()}
{:else}
<p>No content</p>
{/if}
<!-- 6) Pass snippets as PROPS to child components -->
<!-- Parent.svelte -->
<script>
import Layout from './Layout.svelte';
</script>
{#snippet header()}
<nav>Site navigation</nav>
{/snippet}
{#snippet footer()}
<p>© 2026</p>
{/snippet}
<Layout {header} {footer}>
<main>Page content</main>
</Layout>
<!-- Layout.svelte -->
<script>
let { header, footer, children } = $props();
</script>
<header>
{#if header}{@render header()}{/if}
</header>
<main>
{@render children?.()}
</main>
<footer>
{#if footer}{@render footer()}{/if}
</footer>
<!-- 7) Children is implicitly a snippet -->
<!-- Card.svelte -->
<script>
let { children } = $props();
</script>
<div class="card">
{@render children()}
</div>
<!-- Parent -->
<Card>
<h2>Hello</h2>
<p>Content inside</p>
</Card>
<!-- 8) Snippets with typed props (TS) -->
<!-- DataTable.svelte -->
<script lang="ts">
import type { Snippet } from 'svelte';
interface Props<T> {
rows: T[];
renderRow: Snippet<[T, number]>;
renderHeader?: Snippet<[]>;
}
let { rows, renderRow, renderHeader }: Props<unknown> = $props();
</script>
<table>
{#if renderHeader}
<thead>{@render renderHeader()}</thead>
{/if}
<tbody>
{#each rows as row, i (row.id)}
<tr>{@render renderRow(row, i)}</tr>
{/each}
</tbody>
</table>
<!-- Parent.svelte -->
<DataTable rows={users}>
{#snippet renderHeader()}
<tr><th>Name</th><th>Email</th><th>Role</th></tr>
{/snippet}
{#snippet renderRow(user, i)}
<td>{i + 1}. {user.name}</td>
<td>{user.email}</td>
<td>
<span class="badge">{user.role}</span>
</td>
{/snippet}
</DataTable>
<!-- 9) Reusing snippets — export from a module -->
<!-- snippets.svelte.js — DOES NOT EXIST; snippets are template-only -->
<!-- Instead, put snippets in a component and reuse: -->
<!-- shared/UserBadge.svelte -->
<script>
let { user } = $props();
</script>
<span class="badge">
<img src={user.avatar} alt="" />
{user.name}
</span>
<!-- 10) Default content with snippets -->
<!-- Button.svelte -->
<script>
let { children, icon } = $props();
</script>
<button class="btn">
{#if icon}
{@render icon()}
{/if}
{@render children?.()}
</button>
<!-- Parent -->
<Button>Save</Button> <!-- no icon -->
<Button>
{#snippet icon()}
<svg>...</svg>
{/snippet}
Save
</Button>
<!-- 11) Computed snippet selection -->
{#snippet smallText(text)}
<small>{text}</small>
{/snippet}
{#snippet bigText(text)}
<h2>{text}</h2>
{/snippet}
<!-- Conditionally pick a snippet -->
{@const fn = level === 'big' ? bigText : smallText}
{@render fn('Hello')}
<!-- 12) Snippets vs slots — Svelte 5 changes -->
<!--
Slots (Svelte 4 — still supported for compat):
<slot name="header" />
<slot {data} /> <!-- scoped slot -->
Snippets (Svelte 5 — preferred):
let { header, children } = $props();
{@render header?.()}
{@render children?.(data)}
Snippets are more powerful:
- Can pass parameters cleanly
- Fully typed in TypeScript
- Can be passed around as values
- Cleaner conditional rendering
-->
<!-- 13) Real-world example — list with custom row -->
<!-- VirtualList.svelte -->
<script lang="ts">
import type { Snippet } from 'svelte';
interface Props<T> {
items: T[];
itemHeight?: number;
children: Snippet<[T, number]>;
}
let { items, itemHeight = 40, children }: Props<unknown> = $props();
</script>
<div class="virtual-list" style:height={items.length * itemHeight + 'px'}>
{#each items as item, i (item.id)}
<div class="row" style:height={itemHeight + 'px'}>
{@render children(item, i)}
</div>
{/each}
</div>
<!-- Parent -->
<VirtualList items={users}>
{#snippet children(user, i)}
<span>{i + 1}. {user.name}</span>
<button onclick={() => editUser(user.id)}>Edit</button>
{/snippet}
</VirtualList>
<!-- 14) Loading states with snippet variants -->
<AsyncData url="/api/posts">
{#snippet loading()}
<Spinner />
{/snippet}
{#snippet error(err)}
<p class="error">{err.message}</p>
{/snippet}
{#snippet content(data)}
<PostList posts={data} />
{/snippet}
</AsyncData>
<!-- AsyncData.svelte -->
<script>
let { url, loading, error: errorSnip, content } = $props();
let data = $state(null);
let err = $state(null);
$effect(() => {
let active = true;
fetch(url)
.then(r => r.json())
.then(d => { if (active) data = d; })
.catch(e => { if (active) err = e; });
return () => { active = false; };
});
</script>
{#if err && errorSnip}
{@render errorSnip(err)}
{:else if !data && loading}
{@render loading()}
{:else if data && content}
{@render content(data)}
{/if}
<!-- 15) Modifier / decorator pattern -->
<!-- Modal.svelte -->
<script>
let { title, actions, children } = $props();
</script>
<dialog>
<h2>{title}</h2>
<div class="body">{@render children()}</div>
{#if actions}
<footer>{@render actions()}</footer>
{/if}
</dialog>
<!-- Parent -->
<Modal title="Confirm">
<p>Are you sure?</p>
{#snippet actions()}
<button onclick={cancel}>Cancel</button>
<button onclick={confirm} class="danger">Delete</button>
{/snippet}
</Modal>
<!-- 16) Common bugs -->
<!-- • Calling a snippet without {@render} → renders nothing -->
<!-- • Naming conflict: snippet 'children' overrides the implicit children prop -->
<!-- • Passing a snippet to a component expecting different shape — TS catches with Snippet<[Args]> -->
<!-- • Forgetting nullable check (?.) when snippet might not be passed -->
<!-- 17) Best practices -->
<!-- ✅ Use snippets over slots in new Svelte 5 code -->
<!-- ✅ Type snippet params with Snippet<[Type1, Type2]> -->
<!-- ✅ Provide fallback content in the component -->
<!-- ✅ Pass snippets as props for layouts; use children for body -->
<!-- ✅ Use @const + ternary to pick snippets at render time -->
<!-- ✅ Keep snippets focused — one concept per snippet -->
Why it matters
Snippets replace slots in Svelte 5 — cleaner, fully typed, can be passed as values. Use for layouts (header / footer / children), configurable cells in tables, and loading-state variants.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
{#snippet card({ title, body })}
<section><h2>{title}</h2><p>{body}</p></section>
{/snippet}
{@render card({ title: 'Hi', body: 'There' })}
Try it Yourself »
Discussion
Loading…