One way of creating a reusable component is by passing children elements or components to parent components. With UI libraries like React, we can create reusable components with React’s children
prop. But how can we handle passing child data to parent components in Svelte?
In come Svelte slots. We can use Svelte slots to create components that accept and render any children. This way, we will be able to create components that can be used multiple times in our application. Slots are useful because they help keep our codebase DRY. Slots also make maintaining, debugging, and updating components easier.
In this article, we will learn how to compose reusable Svelte components with slots, including the different ways we can use them by looking at some practical code examples.
Let’s see how slots work in practice:
<div class="card"> <h1>I am a reusable box</h1> <slot></slot> </div> <style> .card { width: 300px; border: 1px solid #aaa; border-radius: 2px; box-shadow: 2px 2px 8px rgba(0,0,0,0.1); padding: 1em; margin: 0 0 1em 0; } </style>
In the code above, we created a Card
component. The slot
component allows us to pass child data and content to the Card
component, thereby making it reusable.
We can use the Card
component in App.svelte
and pass in our content:
<script> import Card from './Card.svelte'; </script> <Card> <h1>Hello!</h1> <p>This is a box. It can contain anything.</p> </Card>
We can add fallback content to slots to act as placeholders when the slots are empty.
Say we create a blog post card component. We may want to add a fallback title to each post until the cards receive the actual data. We can do that with slot fallbacks.
Whatever data we pass between the opening and closing tags of the slot
component will be the fallback content:
<!-- Card.svelte --> <div class="card"> <slot> <h1>Fallback Blog Title</h1> </slot> </div> <!-- App.svelte --> <script> import Card from "./Card.svelte"; </script> <Card />
This way, every blog card we have will have the generic “Fallback Blog Title” heading until we pass in the actual data. Slot fallbacks are also useful if you need to set up dummy data for your components while developing.
We can have multiple slots in a Svelte component by using the name attribute on the slot
component.
Let’s assume we want to extend the blog card component. Most blog cards don’t have only titles, they also also have dates and a section with some details of what the post is about.
Let’s set this in different sections of the blog card:
<section> <slot name="title" /> <slot name="date"/> <slot name="content" /> </section>
Here, we composed the Card
component to a blog card using slots. To do that, we set up two named slots, title
and content
.
We used the Card
component in App.svelte
. Then, we looped through the items
array and passed in the title
, date
and content
data to their respective slots like so:
<script> import Card from "./Card.svelte"; const items = [ {title: "Title 1", date: '1-06-2000', content: "Some content content here"}, {title: "Title 2", date: '1-06-2000', content: "Some more content content here"}, ]; </script> {#each items as item} <Card> <h1 slot="title">{item.title}</h1> <span slot="date">{item.date}</span> <p slot="content">{item.content}</p> </Card> {/each}
How do we go about passing multiple components into a named slot? Say we want to create a card header slot with a title and date, how would we do that?
Let’s look at how to handle the following scenario:
<Card> <slot="header">card title</slot> <slot="header">card date</slot> </Card> <!-- âť— Duplicate slot name "header" in <Card> -->
The code above will not work because because duplication of slot names is not allowed. How can we fix it?
The solution lies in using a special Svelte element, Svelte:fragment
. svelte:fragment
allows you to place content in a named slot without wrapping it in a container DOM element. This keeps the flow layout of your document intact.
Let’s refactor the Card
component with svelte:fragment
:
<Card> <svelte:fragment slot="header"> <h1>Card title</h1> <p>Card date</p> </svelte:fragment> </Card>
With svelte:fragment
, we avoid adding needless HTML elements that can affect the layout and styling.
There are times where we may not want to set fallback content for slots, yet want to ensure a slot only renders when there is content in it.
We can do that with the special $$slots
variable. While this may not be a critical feature to add, it can affect the styling and layout of your application if a component renders when it’s not supposed to.
Let’s ensure the Card
component does not have empty slots before rendering it like so:
<div class="card"> {#if $$slots.title} <slot name="title" /> {/if} {#if $$slots.content} <slot name="content">Content</slot> {/if} </div>
Aside from conditionally rendering slot components, we can also use the $$slots
variable to conditionally apply classes to components:
<div class="card"> <slot name="title" class:title-style={$$slots.title} /> {#if $$slots.content} <slot name="content">Content</slot> {/if} </div> <style> .title-style{ color: red; } </style>
The $$slots
variable is an object whose keys are the names of the slots passed in by the parent component, and we can use it to conditionally display or style slot components.
We can use slot props to pass data from the child to parent using the let:
directive of slots. This helps us set up separation of concerns between the parent and the child component.
Say we have an array of employees we want to render in our UI. We set up a Contacts.svelte
component where the employees’ details will be rendered, and call Contacts.svelte
in App.svelte
.
We could store the employees’ data in our App.svelte
file, however, we want to avoid polluting App.svelte
with data it does not need, as this will make it harder to maintain in the future.
Let’s set this up in our code and see how it works:
<!--Contacts.svelte --> <script> const names = ["John", "Jane", "Mary"]; </script> <div class="contact"> <slot {names} /> </div> <!--App.svelte --> <script> import Card from "./Card.svelte"; </script> <Contacts let:names> {#each names as name} <p>{name}</p> {/each} </Contacts>
With this, we can leave the responsibility of handling local state and data to the child component, Contacts.svelte
, and keep our App.svelte
cleaner.
We can also pass data through named slots, like so:
<!--Card.svelte --> <script> let title = "I am the title"; let content = "I am the content"; </script> <div class="card"> <slot name="title" {title} /> <slot name="content" {content} /> </div> <!--App.svelte --> <script> import Card from "./Card.svelte"; </script> <Card> <h1 slot="title" let:title>{title}</h1> <p slot="content" let:content>{content}</p> </Card>
In this article, we have learned how to compose Svelte components with slots. We learned what slots are, how to set up fallback content, and named slots. We also learned how to pass dynamic data to slots through props. Asides from the features of slots, we also looked at some scenarios and how they can be used practically.
Now that you have learned about Svelte slots, I encourage you to dive into the docs, practice, and build some awesome applications.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowReact Native’s New Architecture offers significant performance advantages. In this article, you’ll explore synchronous and asynchronous rendering in React Native through practical use cases.
Build scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.