ZYVOPMulti-Platform Sync
SeriesAI NewsWhy ZyVOPJoin Discord
LoginGet Started
ZYVOPMulti-Platform Sync

The Developer Publishing Hub. Write once, publish everywhere, and make your work citation-ready with built-in SEO, AEO, and GEO discovery support. Zero reader paywalls.

Content

  • Categories
  • Tags
  • Badges
  • Leaderboard
  • Write Article
  • Newsletter

Company

  • About Us
  • Why ZyVOP
  • Changelog
  • Compare Platforms
  • Hashnode vs ZyVOP
  • DEV vs ZyVOP
  • Developer API & CLI
  • Author Handbook
  • Contact

Connect

  • Privacy Policy
  • Terms of Service
  • Cookie Policy
  • DMCA Policy
  • Code of Conduct

ยฉ 2026 ZyVOP. Developer Publishing Hub.

Zero paywalls ยท Full content ownership
All systems operational
HomeSvelte Snippets: Reuse Markup Without a New Component

Svelte Snippets: Reuse Markup Without a New Component

Danny Holloran
Danny Holloran
Senior Developer
September 8, 2026
3 min read
Svelte Snippets: Reuse Markup Without a New Component
#JavaScript#svelte#TypeScript
๐Ÿ‘2

You have a card layout that appears twice in the same component: once wrapped in a link, once bare. The markup is identical apart from the wrapper. For years the Svelte answer was to extract Card.svelte, import it, thread props through it, and accept a new file in your tree for six lines of HTML.

That works, but it is a heavy tool for a light problem. A component brings its own module scope, its own props contract, and its own place in the file system. Sometimes you just want the markup twice. Svelte 5's snippets are the smaller tool, and once you see them as functions that return markup, most of the awkwardness in Svelte's old component-composition story goes away.

A snippet is a function, and {@render} calls it

The syntax is {#snippet name(params)}...{/snippet} to define, {@render name(args)} to call:

{#snippet figure(image)}
  <figure>
    <img src={image.src} alt={image.caption} width={image.width} height={image.height} />
    <figcaption>{image.caption}</figcaption>
  </figure>
{/snippet}

{#each images as image}
  {#if image.href}
    <a href={image.href}>{@render figure(image)}</a>
  {:else}
    {@render figure(image)}
  {/if}
{/each}

Parameters behave like a normal function signature: any number of them, destructuring, default values. The one exception is rest parameters, which are not supported.

Scope is lexical, and this is the part worth internalizing. A snippet can read anything in scope where it was declared โ€” <script> variables, the current {#each} item โ€” and it is visible to its siblings and their children, but not to anything above it. Declare a snippet inside a <div> and you cannot render it outside that <div>. Snippets can also reference themselves, which makes recursive markup pleasant instead of a <svelte:self> puzzle:

{#snippet countdown(n)}
  {#if n > 0}
    <span>{n}...</span>
    {@render countdown(n - 1)}
  {:else}
    <span>๐Ÿš€</span>
  {/if}
{/snippet}

They replace slots, and they take the let: directive with them

Snippets are values, so passing markup into a component is just passing a prop. There are three shapes for this. You can pass a snippet explicitly like any other prop, declare snippets directly inside the component's tags (Svelte turns those into props automatically), or write plain content inside the tags, which becomes the implicit children snippet:

<Table data={fruits}>
  {#snippet header()}
    <th>fruit</th><th>qty</th><th>price</th>
  {/snippet}

  {#snippet row(d)}
    <td>{d.name}</td><td>{d.qty}</td><td>{d.price}</td>
  {/snippet}
</Table>

Inside Table.svelte, those arrive as ordinary props: let { data, header, row } = $props(), then {@render row(d)} in the loop. Optional ones use {@render children?.()}, or an {#if} block when you want fallback content.

Compare that to the Svelte 4 version, where <slot name="row" let:item /> introduced a variable through a directive whose colon meant the opposite of every other colon in the template, and where the variable from one slot was invisible inside a sibling slot. Snippets are functions, so their parameters are just parameters. Slots still work in Svelte 5 but are deprecated.

If you are migrating, budget for two rough edges. <slot name="header" /> becomes {@render header?.()}, but the corresponding <div slot="header"> on the consumer side is silently ignored rather than erroring โ€” the content simply vanishes. And <Component let:prop> now throws, as does forwarding a slot with directives attached (<slot name="a" slot="a" let:abc>), which was legal in Svelte 4.

Typing and the edges

Snippets have a real type. Import Snippet from svelte and give it a tuple of its parameters:

import type { Snippet } from "svelte";

interface Props {
  data: unknown[];
  children: Snippet;
  row: Snippet<[unknown]>;
}

Add generics="T" to the <script lang="ts"> tag and you can tie data: T[] to row: Snippet<[T]>, so the consumer gets a type error for a mismatched row. That is strictly better than what slots ever offered.

Two extras worth knowing. Top-level snippets can be exported from a <script module> block and imported into other components, as long as they do not touch instance-level state (Svelte 5.5.0 and up). And createRawSnippet builds one programmatically, which you will almost never need but which exists when a library has to.

The rule of thumb: reach for a snippet when you are repeating markup, and for a component when the chunk has its own state, its own styles, or its own reason to be tested. The snippet docs cover the remaining corners.

Comments (0)

Login to post a comment.

Danny Holloran
Danny Holloran

Senior Developer

Senior Frontend & Fullstack Developer with 14+ years building performant, scalable web applications. Passionate about architecture, mentorship, and finding the right tool for the job.

Subscribe to Danny Holloran's Newsletter

Direct email dispatches when new stories are published. Zero algorithms.

More from Danny Holloran

View profile

WebNN: The Only Web API That Can Reach Your NPU

Almost every laptop shipped in the last two years has a neural processing unit sitting idle. WebNN is the only web standard that can actually talk to it, and it just hit an updated Candidate Recommendation.

4 minSep 15

GraphQL @oneOf: Exactly One Input, Enforced by the Schema

OneOf Input Objects landed in the September 2025 GraphQL spec, which means the exactly-one-of-these-arguments rule you've been enforcing in resolver code is now something the type system can do for you.

3 minSep 14

node --test: The Test Runner You Already Have Installed

Node's built-in test runner has been stable since v20 and now handles mocking, coverage, watch mode, and TypeScript files. Here's what it does well and where it still falls short.

3 minSep 12

CSS scroll-state() Queries: Styling Stuck, Snapped, and Scrollable

Sticky headers, snapped carousel slides, and scroll shadows have all been JavaScript jobs for a decade. Scroll-state container queries hand that work back to CSS.

3 minSep 7

Async Svelte: Using await Directly in Your Components

Svelte 5.36 lets you use await at the top level of a component, inside $derived, and in your markup. Here is how synchronized updates, boundaries, and $effect.pending() fit together.

4 minAug 31