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 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
2
Answers
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).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.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.)