kit
kit copied to clipboard
Treat layout pages like components when using named slots
Let's say I have a _layout.svelte like the following:
<slot name="layout-pre"></slot>
<div id="intermediate">Some other intermediate stuff.</div>
<slot></slot>
And now I have a child view (let's call it homepage.svelte which inherits that layout, which puts some stuff into the named slot, and the rest into the default slot:
<h1 slot="layout-pre">This should come before #intermediate.</h1>
<!-- When rendered, the div from above should get inserted here from the layout:
<div id="intermediate">Some other intermediate stuff.</div>
-->
<main>Since this doesn't use a named slot, it'll just get inserted into the unnamed slot above.</main>
Right now this is not allowed, and the following error is thrown: Element with a slot='...' attribute must be a descendant of a component or custom element. Are there technical or usage reasons why we wouldn't want to treat a layout like any other component with respect to slots?
I assumed this would work, only to get that error. I think this would be very useful in various situations.
Here's my use case (_layout.svelte):
<aside>
<slot name="aside"></slot>
</aside>
<!-- other markup here, for various reasons -->
<main>
<slot></slot>
</main>
With routes being components, shouldn't this be possible?
I guess it doesn't work because the Svelte compiler needs to know at compile-time which parent-component the slots will end up in.
was looking for this. I'm sad its not posible.
Well, there's nothing keeping us from writing our pages like
<Layout>
<h1 slot="above">...</h1>
<main>derp derp lalalala</main>
</Layout>
Is this the same issue / reason why slot props apparently aren't passed from layout files to components?
but, for example i was unsuccessfully trying to make this :
<named slot>
<Navcomponent in layout>
<slot>
I mean, having 1 slot, then the regular layout in the middle with a navbar, and finally the regular slot. this for having each page to dinamicaly display stuff in two sites
The issue @silllli mentions is confusing me at the moment. Svelte's docs say that <slot>s are meant to be able to pass props
You have to do it in a regular way, by creating a custom layout: (_myLayout.svelte) and include it in your index page.
Can someone show how to include a _customLayout.svelte as zakaria-chahboun suggests?
Can someone show how to include a _customLayout.svelte as zakaria-chahboun suggests?
Example:
We have a route called test.
So we just create a folder named test, and inside of this folder we have our svelte files,
One is our router and the second is our custom layout, like so:
- test
- [id].svelte
- _customLayout.svelte
The _customLayout.svelte is like this:
<slot name="sidebar"></slot>
<slot name="profile"></slot>
And the [id].svelte is like this:
<script context="module">
export async function preload(page) {
const { id } = page.params;
return {id};
}
</script>
<script>
import customLayout from "./_customLayout.svelte";
</script>
<customLayout>
<div slot="sidebar">test</div>
</customLayout>
Is there some technical reasons preventing slots to be implemented by default for layouts defined with _layout.svelte/$layout.svelte?
Bit late to the discussion and it looks like this issue was originally from Sapper, but I've found some good uses for named slots in layouts with Svelte/kit.
For instance, let's image a long blog post with a table of contents sidebar. It would be really helpful if the main contents is the default slot and the sidebar is a named slot. This would make development really easy! But let's imagine a more complex scenario because on mobile sites, this sidebar should disappear and potentially be replaced with a hamburger menu in the menu. Using named slots, this is easy as you can define the name in the $layout.svelte. But without it, every page needs to implement the header as well. This may be a contrived example, but the point stands.
Other's earlier in this thread have suggested you can just create a Layout component, import that onto your page, and then use named slots with that. But…
- Doesn't that defeat the purpose of using a
$layout.svelte? - This will impact performance as everything will get redownloaded and rerendered on page navigation
- Destroys all state that could be shared between pages
- For instance, the YouTube search bar's value persists between page changes
- If your site uses multiple layouts, that means also maintaining multiple layout components and multiple layout pages (if any part of the layout is static)
I agree, without this feature any dynamic sidebar becoming a disaster.
I have the same issue.
My web site is divided in three parts : header, rest, footer.
Logically, header and footer would go in $layout.svelte and rest would go in each page and be rendered in the layout by <slot/>.
However, on mobile devices, I want header and part of the rest to have the height of the viewport. So I would need to have two different slots like so:
<div class="flex flex-col flex-nowrap h-screen sm:h-480px">
<header class="flex-none h-20">
<MainHeader />
</header>
<section class="flex-1">
<slot ="top_slot"/>
</section>
</div>
<section>
<slot />
</section>
<footer class="flex-none h-20">
<MainFooter />
</footer>
I agree with this request. It would create a lot more flexibility on the layout. I expected it to work out of the box. I'm wrapping code in "Layout" components but it does add some boilerplate. I'm using it for adding things like modals, app status messages, notifications, etc.
@Nick-Mazuk I have the same concern specifically about re-rendering on page navigation when using these "Layout" components. I think the named slots in the layout would definitely eliminate the need to create as many additional layouts templates.
@martinjeromelouis it definitely seems like there would be some benefits when dealing with mobile. I haven't gotten that far in my app yet but I'm definitely going to use slots when I get to that point.
This makes you loose lots of performance as this forces full rerenders which is super annoying.
In the pseudocode snippet below I demonstrate how I get around this problem in my project.
<!-- ⚠ untested pseudo-ish code ahead ⚠ -->
<!-- __layout.svelte -->
<script context="module">
export async function load({ page })
{
const slots = {}
if( page.path === '/foo' )
{
slots.navigator = ( await import( `$lib/page-slot-components/foo/navigator.svelte`) ).default
slots.sidemenu = ( await import( `$lib/page-slot-components/foo/sidemenu.svelte`) ).default
}
else
{
slots.navigator = ( await import( `$lib/page-slot-components/bar/navigator.svelte`) ).default
slots.sidemenu = ( await import( `$lib/page-slot-components/bar/sidemenu.svelte`) ).default
}
return {
status: 200,
props: {
slots
}
}
}
</script>
<script>
export let slots
</script>
{ #if slots.navigator }
<svelte:component this={slots.navigator}></svelte:component>
{/if}
{ #if slots.sidemenu }
<svelte:component this={slots.navigator}></svelte:component>
{/if}
This approach is fully SSR compatible and does not produce flashes of content. There are limitations though of course.
Note: svelte-kit currently isn't able to properly parse template literals that also use import aliases like $lib. Actually, import paths with variables in general have issues. So clever dynamic paths for your import() function like src/routes${page.path}/_Navigator.svelte are going to be difficult to pull of, but not impossible. I say give it a shot. There are many different ways to configure this until something like #1459 is implemented
Any updates on this? This is still quite the annoyance.
Still hoping and waiting for this to work. Is this issue on the roadmap or is it not likely to happen?
Still hoping and waiting for this to work. Is this issue on the roadmap or is it not likely to happen?
it is not likey to happen.
In my opinion svelte has some nice ideas but in too many places its inventing too much new syntax. I think e.g. lit does a much better job at the syntax place as it's able to run without any transformation.
This also led to the issue described here I believe
lit does a much better job at the syntax place as it's able to run without any transformation.
and how lit solve such specific issue? Please, talk about this topic only.
lit does a much better job at the syntax place as it's able to run without any transformation.
and how lit solve such specific issue? Please, talk about this topic only.
Iit does not use a custom syntax for routing but just normal components. Therefore all features of normal components still work. I remember working around this issue here by wrapping my pages in a component if I'm not misremembering.
Iit does not use a custom syntax for routing but just normal components.
we also have no custom syntax, __layout is a common svelte component
Iit does not use a custom syntax for routing but just normal components.
we also have no custom syntax, __layout is a common svelte component
I'm not using svelte any more but I believe that according to https://svelte.dev/docs#template-syntax-slot the pages should have a wrapper like (copying the example in the first comment of this issue)
<layout>
<h1 slot="layout-pre">This should come before #intermediate.</h1>
<main>Since this doesn't use a named slot, it'll just get inserted into the unnamed slot above.</main>
</layout>
Therefore I believe svelte pages are not normal components.
Therefore I believe svelte pages are not normal components.
it's a normal component, but putting an actual page into this slot is done by Kit itself and you have no control over it. Basically, Kit makes rendering twice, one for layout and the second for page but the layout and page it's common and normal components.
To solve this issue we should extend interface between layout component and page component.
This is kinda a bummer honestly.
I have a navigation, some content and a footer. I want to be able to set the title of the current page (which is defined in the actual page, e.g. about.svelte, inside <svelte:head><title>...</title></svelte:head>) inside the navigation component and of course the contents of the page.
Now I do imagine I could work around this problem by using a store of some sort and have the navigation two-way-bind to e.g. currentPage#title or something, but that would also mean I have to ensure my about.svelte page writes the title into the store when being loaded?
It'd be much easier if I could somehow specify the target slot inside the layout with the default __layout.svelte file - instead of replacing it.
This actually is so much of an issue, that I feel discouraged working with Svelte any further (for the time being). Thus I will revisit Svelte(Kit) once there's a way to do this. I sort-of wished, that there was no layout-magic happening at all and that the pages and the layout were all just individual components, that have slots, that I can individually address.
Edit: in my particular case I could also just read the head->title attribute, but while this works in this case, it doesn't seem like a general solution.
I have a project that looks something like the following
src/
__layout
map/
new.svelte
[id]/
index.svelte
edit.svelte
user/
[id].svelte
account.svelte
forgot-password.svelte
home.svelte
index.svelte
privacy.svelte
signin.svelte
signup.svelte
terms.svelte
My authenticated and unauthenticated layouts vary quite a bit but the ideal directory structure doesn't offer a clear cut way to override/reset the main layout template without a lot of duplication.
These pages would use the unauthenticated layout: forgot password, index, privacy, signin, signup, terms. The rest would use an authenticated layout.
I hoped that by having one __layout.svelte file with a named slot and an if statement I can do this in a pretty straightforward way.
// __layout.svelte
{#if $$slots.unauthenticated}
// layout for public pages
<slot name="unauthenticated" />
{:else}
// layout for authenticated pages
<slot />
{/if}
// terms.svelte
<div slot="unauthenticated">
// public, terms and conditions content
</div>
Is this the wrong way to think about layouts? Is this in line with what folks in this issue have been requesting or am I misunderstanding something? If I am, it would be lovely to have support for this to make the layout file setup a lot simpler!
EDIT
Is a reasonable alternative to have something like an authenticatedRoutes array in __layout.svelte and conditionally show different UI in the template? This doesn't quite solve what named slots brings to layouts in sveltekit but it at least solves a simpler problem like I'm facing I believe.
<script>
import { page } from '$app/stores';
const publicRoutes = ['/', 'terms', 'privacy', 'signup', 'signin', 'sandbox', 'forgot-password'];
$: isPublic = publicRoutes.includes($page.url.pathname);
</script>
{#if isPublic}
// public layout here
<slot />
{:else}
// authenticated layout
<slot />
{/if}
@wfendler your edit is what I ended up doing. It's lame and feels like a hack, but it does work pretty well.
Just looking over some older issues, we had a need to do this too. I solved it using a few different techniques. We use context (just because it's available), although not a requirement. The context is a writable store. In the store we bind to an html element/DOM node. Then we use a "portal" to project content into the html element/DOM node.
This REPL doesn't use sveltekit (yet) (for obvious reasons), but you could easily see how switching from one page to another could be accomplished with routing and the routed components would project whatever they need to into the targeted html element.
REPL example
Obviously this has tremendous downsides, namely relying on an action, thus not being able to be rendered server side. There is quite a bit of setup involved also, but once its setup, things could easily be componentised to ease the boiler-plate.
Explanation without REPL and no context (plain store):
<script lang="ts">
// navigation is a writable store "writable(null)"
import { navigation } from './stores';
let ref: HTMLElement;
$: navigation.set(ref);
</script>
<!-- Here is the magic -->
<nav bind:this={ref} />
Now you are able to target this specific DOM node and append whatever you want to it, like this:
<script lang="ts">
import { navigation } from './stores';
</script>
<!-- we are currently using `svelte-portal` -->
<Portal target={$navigation}>
Navigation from some component
<ul>
<li>
<a href="/users">Users</a>
</li>
</ul>
</Portal>