sveltekitsvelte-5

Svelte 5 multiple named slots in +layout.svelte


I have taught tutorials about multiple props to component, but I want to use it in +layout.

I hope to insert multiple different components into the specified {@render} of the upper layout (+layout.svelte, not a component) at different routing sub-levels (some +page.svelte files) as like as svelte4 multi named slot.

I currently use the following code to solve the needs, but I intuitively think that my way is bad and very circuitous, and I do not need to dynamically change the inserted components after loading the page. Now I use additional code to pass these snippets via context methods, is there a better way to simplify them?

<!-- +layout.svelte -->
<script lang="ts">
  import { setContext, createRawSnippet } from 'svelte';
  import { writable } from 'svelte/store';

  const dummySnippet = (text: string) =>
    createRawSnippet(() => {
      return { render: () => text };
    });

  let { children } = $props();

  let slotLeft = writable(dummySnippet('LEFT'));
  let slotCenter = writable(dummySnippet('CENTER'));
  let slotRight = writable(dummySnippet('RIGHT'));

  setContext('LayoutSlot', { slotLeft, slotCenter, slotRight });
</script>

<winbox class="flex flex-row h-full w-full overflow-hidden">
  <listbox class="w-[400px]">
    {@render $slotLeft()}
  </listbox>

  <div class="flex-1 flex flex-col border-x">
    {@render $slotCenter()}
  </div>

  <div class="w-[350px]">
    {@render $slotRight()}
  </div>
</winbox>

{@render children()}
<!-- +page.svelte -->
<script lang="ts">
  import { onMount, getContext, type Snippet } from 'svelte';
  import type { Writable } from 'svelte/store';

  let {
    slotLeft,
    slotCenter,
    slotRight
  }: {
    slotLeft: Writable<Snippet>;
    slotCenter: Writable<Snippet>;
    slotRight: Writable<Snippet>;
  } = getContext('LayoutSlot');

  onMount(() => {
    $slotLeft = menuLeft;
    $slotCenter = mainContent;
    $slotRight = menuRight;
  });
</script>

{#snippet menuLeft()}
  <p>Left Menu Pending</p>
{/snippet}

{#snippet mainContent()}
  <p>Center Content Pending</p>
{/snippet}

{#snippet menuRight()}
  <p>Right Menu Pending</p>
{/snippet}

<children>TEST</children>

<!-- .... other option snippets from other routing-->
<!-- {#snippet foo1()} -->
<!-- {#snippet foo2()} -->

Hopefully someone can give me idea for better than my current approach to simplify the codes, maybe there should be a direct way to pass props (not only default children props) in the +page.svelte inside html area, but I can't find it.


Solution

  • As of now this is not supported (issue tracking the topic).

    Before Svelte 5 you could not do this at all and this is rather hacky. It does not work properly with server-side rendering because the rendering of the layout finishes before the page can send content back up.

    There is a way to work around this by running the page first, not rendering anything in it, then render the slots from the context. (Found by zyxd on GitHub.)

    E.g.

    <!-- +layout.svelte -->
    <script>
      import { setContext } from 'svelte';
    
      const { children } = $props();
    
      const slots = {};
      setContext('layout-slots', slots);
    </script>
    
    <!--
      Run page logic first, this should not output anything
      and only set slots in the context.
    -->
    {@render children()}
    
    <div class="box">
      <div class="left">
        {@render slots.left()}
      </div>
      <div class="right">
        {@render slots.right()}
      </div>
    </div>
    
    {@render slots.children()}
    
    <!-- +page.svelte -->
    <script>
      import { getContext } from 'svelte';
    
      // This block could be extracted to a function taking the slots
      // as arguments for convenience.
      const slots = getContext('layout-slots');
      Object.assign(slots, { left, right, children });
    </script>
    
    {#snippet left()}
      <div>Left slot</div>
    {/snippet}
    
    {#snippet right()}
      <div>Right slot</div>
    {/snippet}
    
    {#snippet children()}
      <div>Children slot</div>
    {/snippet}
    

    Alternatively, create a component with the necessary slots and use that in every page instead of doing this via the +layout.svelte.