skip to Main Content

New to SvelteKit and trying to figure this part out…

Here’s a couple of ways I was trying to achieve this, but the sidebar either doesn’t load, or the navigation appears twice for some reason; with the snippet text only appearing in the second instance. I started searching Github to review examples via path:**/+layout.svelte /@render children/ and path:**/+layout.svelte /children: Snippet; *: Snippet, but there are no results that actually import and use duplicate renders; from what I’ve seen. I planned to use +layout.svelte to breakdown my site into Header, Sidebar, Main and Footer and dynamically change the content based on +page.svelte. The docs touch on this and I’ve seen it done with cards and +page.svelte, but not in +layout.svelte itself. Am I doing this wrong?

+layout.svelte

<script lang="ts">
    import "../app.css";
    import type { Snippet } from "svelte";
    import { sineIn } from "svelte/easing";
    import { Drawer, Sidebar } from "flowbite-svelte";

    // Destructure specific props from $props()
    interface Props {
        children?: Snippet;
        filterSidebar?: Snippet<[() => void]>;
    }
    let { 
        children, 
        filterSidebar
    }: Props = $props();

    const transitionParams = {
        x: -320,
        duration: 200,
        easing: sineIn
    };

    let hidden = $state(true);

    function toggleSidebar(): void {
        hidden = !hidden;
    }
</script>
<div class="header flex items-center justify-between p-5 border-b">
    <h1 class="text-xl">SvelteKit Render Test</h1>
    <button
        aria-label="Toggle Filters"
        onclick={toggleSidebar}
    >
        <svg
            class="w-6 h-6"
            xmlns="http://www.w3.org/2000/svg"
            fill="none"
            viewBox="0 0 24 24"
            stroke="currentColor"
        >
            <path
                stroke-linecap="round"
                stroke-linejoin="round"
                stroke-width="2"
                d="M4 6h16M4 12h16m-7 6h7"
            />
        </svg>
    </button>
</div>
<Drawer transitionType="fly" {transitionParams} bind:hidden={hidden} id="sidebar">
    <Sidebar asideClass="w-full">
        {@render filterSidebar?.(toggleSidebar)}
    </Sidebar>
</Drawer>
<main>
    {@render children?.()}
</main>


+page.svelte v1

<script lang="ts">
    import Layout from "./+layout.svelte";
    import { CloseButton, SidebarGroup } from "flowbite-svelte";=
</script>
{#snippet filterSidebar(toggleSidebar: () => void)}
    <SidebarGroup ulClass="flex items-center">
        <h1>Filters</h1>
        <CloseButton onclick={toggleSidebar} />
    </SidebarGroup>
{/snippet}
<h1>Test Element</h1>

Results +page.svelte v1

+page.svelte v1 - blank sidebar


+page.svelte v2

<script lang="ts">
    import Layout from "./+layout.svelte";
    import { CloseButton, SidebarGroup } from "flowbite-svelte";
    console.log("Page rendered");
</script>
<Layout>
    {#snippet filterSidebar(toggleSidebar: () => void)}
        <SidebarGroup ulClass="flex items-center">
            <h1>Filters</h1>
            <CloseButton onclick={toggleSidebar} />
        </SidebarGroup>
    {/snippet}
    <h1>Test Element</h1>
</Layout>

Results +page.svelte v2

+page.svelte v2 - duplicated content

2

Answers


  1. As far as I know, SvelteKit doesn’t provide any way for a +page.svelte file to pass information to a +layout.svelte file. You can use a global store that the +page.svelte file writes values to and the +layout.svelte file reads values from, or similar, but not a very clean solution IMO.

    Another workaround is that you don’t use +layout.svelte files at all, and instead create your layout as an ordinary Svelte component (e.g. $lib/Layout.svelte), and then in each of your +page.svelte files uses that <Layout> component. This way, you can pass any snippets you want to your <Layout> component, but the downside is that you aren’t taking advantage of the layout system available in SvelteKit, so performance might hurt a little, but I don’t think that will be noticeable (I’m guessing the <Layout> component will be deleted and recreated on each navigation).

    Login or Signup to reply.
  2. I will reiterate my answer over here, though it is more a workaround than a proper solution: You can use a context to communicate between page and layout and use {@render children()} only to retrieve snippets from the page for rendering in the layout.

    // $lib/layout-slots.svelte.js
    import { getContext, setContext } from 'svelte';
    
    const key = Symbol('layout-slots');
    
    export function initSlots() {
        const slots = $state({});
        return setContext(key, slots);
    }
    
    export function setSlots(slots) {
        const context = getContext(key);
        Object.assign(context, slots);
    }
    
    <!-- +layout.svelte -->
    <script>
        import { initSlots } from '$lib/layout-slots.svelte.js';
    
        let { children } = $props();
    
        const slots = initSlots();
    </script>
    
    <!--
        run page logic first to get slot snippets,
        the page should have no output.
    -->
    {@render children()}
    
    <h1>{@render slots.heading()}</h1>
    
    {@render slots.children()}
    
    <!-- +page.svelte -->
    <script>
        import { setSlots } from '$lib/layout-slots.svelte.js';
    
        setSlots({ heading, children });
    </script>
    
    {#snippet heading()}
        Hello there
    {/snippet}
    
    {#snippet children()}
        Main slot content
    {/snippet}
    

    SvelteLab

    (And no, this was not possible before with slots, it is just how SvelteKit in particular works. If you nest specific, regular components, you can use slots or snippets to compose them however you want, but this is not the case for layouts and pages which have a very generic relationship that only provides the single default slot/snippet. See this issue.)

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search