Snippets and Content

Chapter 8
Snippets and Content
Passing markup into a child — children, named snippets, and parameterised snippets

Props pass data into a child; content passes markup. Svelte 5's mechanism is the children snippet plus the {#snippet} block — its replacement for the old <slot> system, covering React's children, Vue's slots, and Angular's <ng-content> all at once. It's the most distinctive of the four frameworks' approaches, and worth taking slowly.

The Default children Snippet

<!-- Card.svelte --> <script> let { children } = $props(); </script> <div class="card"> {@render children()} </div>
<!-- parent --> <Card> <h2>Hello!</h2> <p>Markup passed in from outside.</p> </Card>

Whatever a parent writes between a component's tags becomes a special prop called children — a snippet (a reusable chunk of markup). The child renders it with {@render children()}. So children is React's {children}, but instead of just embedding it, you call it via {@render} — because a snippet is technically a renderable function. Card doesn't know what's inside; it just renders whatever it's handed.

{@render} and {#snippet} are the two halves
Svelte 5 replaced slots with two pieces: {#snippet name()}...{/snippet} defines a chunk of markup, and {@render name()} renders one. The default children snippet (the content between tags) is just the most common case. Everything in this chapter is built from these two primitives.

Named Snippets — Multiple Insertion Points

<!-- Panel.svelte --> <script> let { header, children, footer } = $props(); </script> <div class="panel"> <header>{@render header()}</header> <main>{@render children()}</main> <footer>{@render footer()}</footer> </div>
<!-- parent: define named snippets inside the component --> <Panel> {#snippet header()}<h2>Title</h2>{/snippet} <p>Body (the default children).</p> {#snippet footer()}<small>Footer</small>{/snippet} </Panel>

For several insertion points, the parent defines named snippets with {#snippet header()}...{/snippet} inside the component tags — and they arrive as props of those names. Anything not in a named snippet becomes the default children. This is the equivalent of Vue's named slots and React's named-JSX-props approach, expressed through the snippet system.

Fallback Content

{#if children} {@render children()} {:else} <p>No content provided.</p> {/if}

Because snippets are just props (a value, or undefined if not passed), fallback content is a plain {#if} check — render the snippet if it exists, otherwise show a default. More explicit than Vue's "put fallback inside the slot tag," but using only tools you already know.

Parameterised Snippets — Passing Data Back

<!-- List.svelte --> <script> let { items, row } = $props(); </script> <ul> {#each items as item (item.id)} <li>{@render row(item)}</li> <!-- pass each item to the snippet --> {/each} </ul>
<!-- parent: the snippet receives the item --> <List items={people}> {#snippet row(person)} <strong>{person.name}</strong> {/snippet} </List>

The most powerful case: a snippet can take parameters. The child calls {@render row(item)}, passing data out to the markup the parent provided; the parent's {#snippet row(person)} receives it. List owns the looping and data; the parent decides exactly how each row looks. This is the direct equivalent of React's render props and Vue's scoped slots — and notably, it's the same snippet syntax as the simpler cases, not a separate feature.

One mechanism for everything React split three ways
React used children for simple content, named props for multi-region layouts, and render props for "pass data to the caller's markup." Svelte 5's snippets do all three with one consistent primitive — and snippets can even be defined and reused within a single component (a chunk of repeated markup rendered in several places), which the others can't do as cleanly.
{@render}, not just embedding — and <slot> is gone
Two things to watch coming from the old model or other frameworks: a snippet must be rendered with {@render name()} (with parentheses — it's a call), not embedded like a value. And Svelte 4's <slot> / <slot name="x"> / let: system is removed in Svelte 5 — snippets replace all of it. If you see <slot> in a tutorial, that's pre-v5 code.
Svelte 5ReactVueAngular
children + {@render}{children}default <slot><ng-content>
named snippetsnamed JSX propsnamed slots<ng-content select>
parameterised snippetrender propsscoped slotstemplate context
reuse a snippet in-component— (extract a component)

Coding Challenges

Challenge 1

Build a Card component that renders its children snippet inside a styled div, then use it twice with completely different inner markup. Add a {#if children} fallback shown when no content is passed.

📄 View solution
Challenge 2

Build a Panel component with header and footer named snippets plus the default children for the body, and fill all three from a parent using {#snippet header()} / default content / {#snippet footer()}.

📄 View solution
Challenge 3

Build a List component that takes an items array and a row snippet, looping over the items and calling {@render row(item)} for each. In the parent, define {#snippet row(person)} to render each item your own way (e.g. bold name + muted id).

📄 View solution

Chapter 8 Quick Reference

  • children prop + {@render children()} — content between tags (= React's {children})
  • {#snippet name()}...{/snippet} defines markup; {@render name()} renders it
  • Named snippets arrive as same-named props — for multi-region layouts (= named slots)
  • Fallback — a plain {#if children}...{:else}...{/if}
  • Parameterised snippets ({@render row(item)}{#snippet row(x)}) = render props / scoped slots
  • One mechanism covers children, named regions, and data-passing; Svelte 4's <slot> is removed
  • Next chapter: lifecycle — onMount, onDestroy, and $effect for teardown