Lesson 9 / 25

Snippets and {@render}

Reusable markup and the replacement for slots.

Passing markup to components

A snippet is a named, reusable chunk of markup declared with {#snippet name(params)}...{/snippet} and rendered with {@render name(args)}. Content placed between a component's tags becomes the children snippet prop; snippets declared inside the component tags become props with the same name. Snippets are typed with Snippet<[ParamTypes]> from svelte. Optional snippets are rendered with {@render footer?.()} or guarded with {#if}. In Svelte 4 this was done with <slot />, named slots (slot="header") and let: directives; slots still work in Svelte 5 for compatibility but snippets are the recommended approach.

A generic table with row snippets

children and named snippet props.

<!-- Table.svelte -->
<script lang="ts" generics="T">
  import type { Snippet } from 'svelte';

  let { rows, header, row, empty }: {
    rows: T[];
    header: Snippet;
    row: Snippet<[T]>;
    empty?: Snippet;
  } = $props();
</script>

<table>
  <thead><tr>{@render header()}</tr></thead>
  <tbody>
    {#each rows as item}
      <tr>{@render row(item)}</tr>
    {:else}
      {@render empty?.()}
    {/each}
  </tbody>
</table>

<!-- Usage -->
<Table rows={users}>
  {#snippet header()}<th>Name</th><th>Email</th>{/snippet}
  {#snippet row(u)}<td>{u.name}</td><td>{u.email}</td>{/snippet}
  {#snippet empty()}<tr><td colspan="2">No users</td></tr>{/snippet}
</Table>

Snippets work locally too

Declare a snippet in the same component to avoid repeating a block of markup in several branches; it can read the surrounding state.

Quick check: Content placed between <Card> and </Card> is available to Card as...

  • A string prop called content
  • A $slots object
  • The children snippet prop, rendered with {@render children()}
  • The default export of the component
Answer

The children snippet prop, rendered with {@render children()} — Snippets replace the default slot.