javascriptsignalssveltesveltekitsvelte-5

How do we do "named slots" in in Svelte 5?


Svelte 5 Slots are Confusing

Now, with Svelte 5 at pre-release, I feel I can express some confusion about slots in Svelte 5. In Svelte 4, slot and names slots where done in the following way:

ContactCard.svelte

<article class="contact-card">
    <h2>
        <slot name="name">
            <span class="missing">Unknown name</span>
        </slot>
    </h2>

    <div class="address">
        <slot name="address">
            <span class="missing">Unknown address</span>
        </slot>
    </div>

    <div class="email">
        <slot name="email">
            <span class="missing">Unknown email</span>
        </slot>
    </div>
</article>

<style>...</style>

+page.svelte

<script>
    import ContactCard from './ContactCard.svelte';
</script>

<ContactCard>
    <span slot="name"> P. Sherman </span>

    <span slot="address">
        42 Wallaby Way<br />
        Sydney
    </span>
</ContactCard>

Pretty easy and straight forward in my humble opinion.

Svelte 5 Single Slot example

Component.svelte

<script>
  let { children } = $props();
</script>

<div>
  {@render children()}
</div>

+page.svelte

<script>
  import { Component } from '$lib';
</script>

<Component>
  ...content...
</Component>

Also easy...

My Question...

How do we do "named slots" in this new paradigm


Solution

  • You just define a #snippet inside the component tag.
    The snippet will be passed as a prop with the same name. E.g.:

    <script>
        import ContactCard from './ContactCard.svelte';
    </script>
    
    <ContactCard>
        {#snippet name()} P. Sherman {/snippet}
    
        {#snippet address()}
            42 Wallaby Way <br>
            Sydney
        {/snippet}
    </ContactCard>
    

    (If you need the span elements, you can add them inside the snippet.)

    <!-- ContactCard.svelte -->
    <script>
      let { name, address } = $props();
    </script>
    
    <div>
      <div class="name">{@render name()}</div>
      <div class="address">{@render address()}</div>
    </div>
    

    Playground

    For fallback content, you can check whether a snippet was passed e.g. using {#if} or define a fallback snippet.

    <div class="name">
        {@render name?.()}
        {#if !name}
            ...
        {/if}
    </div>
    
    <div class="name">
        {#snippet nameUnknown()}...{/snippet}
        {@render (name ?? nameUnknown)()}
    </div>
    

    Snippets can be defined outside the component and be passed explicitly as well:

    {#snippet name()} P. Sherman {/snippet}
    {#snippet address()}
        42 Wallaby Way <br>
        Sydney
    {/snippet}
    <ContactCard {name} {address} />
    

    Scoping works similarly to variables; snippets are available on the same level and deeper in their element/block.


    Side note: The direct content of a component just implicitly defines a snippet called children. If you need to parameterize the snippet, you will also have to define it more explicitly, e.g.:

    <FancyList items={users}>
        {#snippet children(item)}
            {item.lastName}, {item.firstName}
        {/snippet}
    </FancyList>
    

    At this point you could also just call it something more specific, like maybe itemTemplate for this example.


    TypeScript

    The type Snippet can be imported from 'svelte', it is generic to support arguments but defaults to snippets without any.

    E.g.

    <script lang="ts">
        import type { Snippet } from 'svelte';
        let { name, address }: {
            name: Snippet,
            address?: Snippet,
        } = $props();
    </script>
    

    Use ? for the prop to mark it optional as usual.

    Arguments are listed as the generic type parameter in an array.

    Example for a generic list component whose item template has an argument for the current item:

    <script lang="ts" generics="T">
        import type { Snippet } from 'svelte';
        let { items, itemTemplate }: {
            items: T[],
            itemTemplate: Snippet<[T]>,
        } = $props();
    </script>
    
    <div class="list">
        {#each items as item}
            <div class="item">{@render itemTemplate(item)}</div>
        {/each}
    </div>