Svelte Snippets - Say Goodbye To Slots
| Published: February 23, 2024
| Updated: February 23, 2024
Everyone is talking about the upcoming release ofSvelte 5 and the major changes brought with it including the newRunes syntax. But there’s also another big change to how you’ll create Svelte components, and that is with the newSnippets feature.
The introduction of snippets brings with it thedeprecation of the<slot /> tag, which allowed you to pass markup to a component aschildren. Svelte developers will now have to re-learn this new paradigm, which massively improves dev-experience; particularly for library creators.

This article will walk you through:
- How to use snippets,
- How you can achieve the same results using snippets as you would have previously using
<slot />, and - Some examples showing the power of snippets and how they allow us to write more readable code.
What is a Svelte Snippet?
Snippets are simply a way to create reusable chunks of markup inside your Svelte components. Snippets can be passed to other components as props or children, similarly to how you would use a<slot /> tag. They even accept parameters to render dynamic content or be used with{#each} blocks.
Learning this new paradigm is best conceptualized by looking at some examples of how write snippets, so in the next section we’ll look at 3 examples snippets:
- Basic Usage Example - No parameters,
- Slight more advanced example - with parameters,
- Advanced Example - recreating a UI component using Snippets
In thelast example, we’ll first look at how something is currently achieved using<slot /> (Svelte 4 and earlier), and the compare it to the approach with snippets (Svelte 5).
Basic Usage
To create a snippet, simply wrap a chunk of markup inside some{#snippet} tags, like this:
{#snippetsnippetName()}<!-- Snippet Content -->{/snippet}And you can render the snippet markup using{@render snippetName()}, for example:
{#snippetsnippetName()}<p>This is the snippet content</p>{/snippet}{@rendersnippetName()}And now you can use this snippet anywhere you like, including passing it into other components like this:
<ChildComponent{snippetName}/>Which you can accept as props the child component using the$props() rune, like this:
ChildComponent.svelte
<script>let{ snippetName}=$props();</script><div>{@rendersnippetName()}</div>Nice one! This is how you pass HTML chunks into other components using snippets.
But this example isn’t entirely useful, so let’s look at a more realistic example where we can pass arguments to the snippet, and use it inside an{#each} block.
Using Snippets with parameters
To create a snippet with parameters, you just add arguments into the snippet declaration:
{#snippetuserPreview(user, buttonText, buttonAction)}<div><h2>{user.name}</h2><imgsrc={user.profileImage}alt="Profile Image"/><buttononClick={()=>buttonAction(user.id)}>{buttonText}</button></div>{/snippet}Now let’s pass the snippet and the relevant data into 2 child components:
<FriendsList />and<SuggestedFriendsList />
+page.svelte
<script lang='ts>letfriendsData:{id: string; name: string; profileImage: string}[]=[]letsuggestedFriendsData:{id: string; name: string; profileImage: string}[]=[]</script>{#snippetuserPreview(user, buttonText, buttonAction)}<div><h2>{user.name}</h2><imgsrc={user.profileImage}alt="Profile Image"/><buttononClick={()=>buttonAction(user.id)}>{buttonText}</button></div>{/snippet}<FriendsList{friendsData}{userPreview}/><SuggestedFriendsList{suggestedFriendsData}{userPreview}/>Let’s look at how to use the passed snippet in the<SuggestedFriendsList /> child component:
SuggestedFriendsList.svelte
<scriptlang="ts">import type{ Snippet}from'svelte';import type{ UserType}from'$lib/types.d.ts'let{ friendsData, userPreview}= $props<{ friendsData:{id: string; name: string; profileImage: string}[]; userPreview: Snippet<{user: UserType,buttonText: string;buttonAction:(id: string)=>void}>}>();asyncfunctionaddUserAsFriend(id){await db.addFriend(id)}</script><h1>Your Friends</h1>{#eachfriendsDataasfriend}{@renderuserPreview(friend,"Add Friend", addUserAsFriend)}{/each}Now you have passed the relevant markup into the child component, and you can declare as much or as little data in the snippet as you need.
The implementation of the<FriendsList /> component is trivial and could resemble something similar to the<SuggestedFriendsList /> component, however instead thebuttonAction could delete a friend, and the button text would correspond. We just split them up to illustrate how we can reuse the markup and maintain flexibility.
Now we know how to provide arguments to snippets, let’s look at something a little more advanced.
Using Svelte Snippets - Advanced Example
In this example, we’ll look at a real life example of porting a React UI component to Svelte. This example is from theAceternity Svelte library I ported over fromAceternity UI, which was originally created with, and for React. More specifically, we’ll be looking at theAceternityTabs component.
Seeing as the Svelte port was made with Svelte 4, I didn’t have access to snippets - so we will highlight how the lack of snippets lead to untidy and duplicated code. Furthermore, we’ll show howwith snippets, we can create a more 1-to-1 translation from React components.
In the interest of code cleanliness, we’ll remove tailwind class styles and some functionality from the components.
In theoriginal React component, the component source code (components/ui/tabs.tsx) is one file:
REACT component source code
'use client';import{ useState}from'react';import{ AnimatePresence, motion}from'framer-motion';import{ cn}from'@/utils/cn';typeTab={title:string;value:string;content?:string| React.ReactNode|any;};exportconstTabs=({tabs: propTabs,containerClassName,activeTabClassName,tabClassName,contentClassName}:{tabs: Tab[];containerClassName?:string;activeTabClassName?:string;tabClassName?:string;contentClassName?:string;})=>{const[hovering, setHovering]=useState(false);return(<><div>{propTabs.map((tab, idx)=>(<button>{active.value=== tab.value&&<motion.div/>}<span>{tab.title}</span></button>))}</div><FadeInDivtabs={tabs}active={active}key={active.value}hovering={hovering}/></>);};exportconstFadeInDiv=({className,tabs,hovering}:{className?:string;key?:any;tabs: Tab[];active: Tab;hovering?:boolean;})=>{constisActive=(tab: Tab)=>{return tab.value=== tabs[0].value;};return(<div>{tabs.map((tab, idx)=>(<motion.div>{tab.content}</motion.div>))}</div>);};To port this over without snippets, we have to split the 2 JSX components (in this 1 file) into 2 files. We had to move theFadeInDiv to its own.svelte component file, and then import it in the mainTabs.svelte component, like this:
components/Tabs.svelte
<scriptlang="ts">import{ Motion}from'svelte-motion';import FadeInDivfrom'./FadeInDiv.svelte';type Tab={title: string;value: string;content?: string| any;};exportletpropTabs: Tab[];exportletcontainerClassName: string|undefined=undefined;exportletactiveTabClassName: string|undefined=undefined;exportlettabClassName: string|undefined=undefined;exportletcontentClassName: string|undefined=undefined;letactive: Tab= propTabs[0];lettabs: Tab[]= propTabs;let hovering=false;</script><div>{#eachpropTabsastab, idx(tab.title)}<button>{#if active.value=== tab.value}<Motion><div/></Motion>{/if}<span>{tab.title}</span></button>{/each}</div><FadeInDiv{tabs}{hovering}/>So, without snippets, we’ve had to move our code into 2 separate files - even if the<FadeInDiv /> component wouldonly be used in this<Tabs /> component.
Now,with snippets, we could just convert the<FadeInDiv /> markup into asnippet:
{#snippetfadeInDiv(className, tabs, hovering)}<div>{#eachtabsastab, idx}<motion.div>{tab.content}</motion.div>{/each}</div>{/snippet}and then call it from the same file:
components/Tabs.svelte
<scriptlang='ts'>// All the script content</script><!-- All the additional tabs content -->{@renderfadeInDiv('mt-32', tabs, hovering)}{#snippetfadeInDiv(className, tabs, hovering)}<div>{#eachtabsastab, idx}<motion.div>{tab.content}</motion.div>{/each}</div>{/snippet}So long as the snippet is within the same lexical scope as the markup using it, we’ve now been able to move the 2 files (Tabs.svelte andFadeInDiv.svelte) into the 1 file. 🎉
So far, we’ve only looked at thesource code of the component. Now let’s look at the how you would use this component in one of your pages.
In the original React usage code, you just have to declare the tabs array and then call<Tabs tabs={tabs} />, like this:
REACT Usage of Tabs component
exportfunctionTabsDemo(){const tabs=[{title:'Product',value:'product',content:(<div><p>Product Tab</p><DummyContentimageSrc="https://placehold.co/800x600"/></div>)},{title:'Services',value:'services',content:(<div><p>Services tab</p><DummyContentimageSrc="https://placehold.co/600x400"/></div>)}];return<Tabstabs={tabs}/>;}constDummyContent=(imageSrc)=>{return<Imagesrc={imageSrc}/>;};There’s quite a bit going on here which Svelte 4 can’t handle simply.

Firstly, we need to deal with theDummyContent component at the bottom of the file. For this, we have 2 options:
- Create a new
DummyContent.sveltecomponent, and import it in our page, or - Copy the markup of the
DummyContentwherever it is used, without care for duplicate code.
With the example above, it seems obvious that you should just replace the<DummyContent /> declarations with the actual markup of the component. However, in this example a lot of the functionality has been stripped, and as it gets more verbose (or the list gets larger), it’s better to separate it off as its own component. You don’t want massive blobs of duplicative code.
Secondly, with Svelte you can’t use markup as a property value in your objects, as it is being done in thecontent property for eachTab.
So in my port, I converted thecontent in eachTab to astring. Then using thespecial{@html} tag, I was able to render the string as HTML in theTabs.svelte component, mentioned earlier:
<scriptlang="ts">import Tabsfrom'./Tabs.svelte';const tabs=[{title:'Product',value:'product',content:'<div> <p>Product Tab</p> <img src="/linear.webp" /> </div>'},{title:'Services',value:'services',content:'<div> <p>Services tab</p> <img src="/linear.webp" /> </div>'}];</script><TabspropTabs={tabs}contentClassName="rounded-2xl"/>Ialsomoved theDummyContent markup into the content value. 🤦 As I mentioned, it’s definitely not clean - but with Svelte 4, it’s likely the cleanest way to get around this.
Writing stringified HTML as a prop is unpleasant and in most cases should be avoided. It’s prone to errors, you don’t get syntax highlighting, and could potentiallyopen users up to XSS vulnerabilities if the data comes from an untrusted source and isn’t sanitized.
So now with Svelte 5 snippets, we could convert theDummyContent component, and the content props to snippets:
{#snippetdummyContent(imageSrc)}<imgsrc={imageSrc}/>{/snippet}{#snippetcontent(title, imageSrc)}<div><p>{title} Tab</p>{@renderdummyContent(imageSrc)}</div>{/snippet}And then we can get rid of the content property in ourtabs array, and pass thecontent snippet to the<Tabs /> component:
<scriptlang="ts">import Tabsfrom'./Tabs.svelte';const tabs=[{ title:'Product', value:'product', imageSrc:'https://placehold.co/800x600',},{ title:'Services', value:'services', imgSrc:'https://placehold.co/600x400',},];</script>{#snippetdummyContent(imageSrc)}<imgsrc={imageSrc}/>{/snippet}{#snippetcontent(title, imageSrc)}<div><p>{title} Tab</p>{@renderdummyContent(imageSrc)}</div>{/snippet}<TabspropTabs={tabs}contentClassName="rounded-2xl"{content}/>There are a few benefits here:
- We don’t have to create a separate component file for
DummyContent, and - We don’t have to use stringified HTML in our
tabsobjects.
And then we can adjust theTabs.svelte file to accept the snippet like this:
<scriptlang='ts'>import type{ Snippet}from'svelte';let{propTabs, contentClassName, content}= $props<{propTabs: Tab;contentClassName: string;content: Snippet<{title: string; imageSrc: string;}[]>;}></script><!-- All the additional tabs content -->{@renderfadeInDiv('mt-32', tabs, hovering)}{#snippetfadeInDiv(className, tabs, hovering)}<div>{#eachpropTabsastab, idx}<motion.div>{@rendercontent(tab.title, tab.imageSrc)}</motion.div>{/each}</div>{/snippet}And now we’ve moved the old Svelte 4 way of porting over this component with slots and chaos, to now using snippets.
In Summary
Snippets are a much more powerful and flexible upgrade from<slot /> feature from Svelte 4 and earlier. As we’ve seen, the benefits snippets bring are:
- being able to effectively use as many ‘components’ as we want in 1
.sveltecomponent file, - pass in HTML blocks as arguments to child components without having to stringify the HTML value,
- handle duplicate code beautifully,
- Add markup as children - similarly to the
<slot />tag.
It’s an awesome feature which has been overshadowed with the monolithic change brought byrunes, however Snippets will make building apps - and moreso buildinglibraries, a much better experience with Svelte.
Remember,Svelte 5 isn’t quite ready yet, so you can play with Snippets in theSvelte 5 preview playground, orcreate a project using the Svelte 5 preview.