Nefe James Nefe is a frontend developer who enjoys learning new things and sharing his knowledge with others.

A comprehensive guide to Svelte components with slots

5 min read 1445

Introduction

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.

Using Svelte slots

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>

Slot fallbacks

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 made a custom demo for .
No really. Click here to check it out.

Named slots

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}

Named slots and Svelte fragments

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.

Conditionally display slots

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.

Passing data across slots through props

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>

Conclusion

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.

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Nefe James Nefe is a frontend developer who enjoys learning new things and sharing his knowledge with others.

Leave a Reply