The simplest, most developer-friendly portal system for Svelte 5.
svelte-bay allows you to easily teleport content from anywhere in your app to specific "bays" (portals) in your layout using the power of Svelte 5 Runes and Context.
$state, $effect) for maximum performance.setContext to ensure state is scoped to the current request tree.npm install svelte-bay
# or
bun add svelte-bay
The easiest way to get started - one command does it all:
# Run this in your SvelteKit project directory
npx svelte-bay init
The CLI will:
svelte-bay (asks which package manager to use)+layout.svelte (or create it if missing)createBay() import and callThat's it! You're ready to use <Portal> and <Pod> components.
💡 Tip: You can skip this step by running
npx svelte-bay init
In your root layout (usually src/routes/+layout.svelte), initialize the system. This sets up the context for your app.
<script lang="ts">
import { createBay } from 'svelte-bay';
// Initialize the bay system once at the root
createBay();
let { children } = $props();
</script>
{@render children()}
Place a <Portal /> wherever you want content to appear. Give it a unique name.
<script>
import { Portal } from 'svelte-bay';
</script>
<header class="flex justify-between p-4">
<h1>My App</h1>
<!-- Content sent to 'header-actions' will appear here -->
<div class="actions">
<Portal name="header-actions" />
</div>
</header>
From any component in your app, use a <Pod /> to teleport content to a portal.
<script>
import { Pod } from 'svelte-bay';
</script>
<Pod to="header-actions">
<button class="btn-primary">Save Changes</button>
</Pod>
<Pod to="header-actions">
<button class="btn-secondary">Cancel</button>
</Pod>
createBay(): Creates a reactive $state registry and shares it via setContext.<Pod />: Registers its children snippet to the registry key matching its to prop.<Portal />: Listens to the registry and renders all snippets registered to its name.By default, svelte-bay works with any string for portal names. If you want full type safety and autocomplete, you can use our Vite plugin.
vite.config.ts:import { sveltekit } from "@sveltejs/kit/vite";
import { defineConfig } from "vite";
import { svelteBay } from "svelte-bay/vite";
export default defineConfig({
plugins: [sveltekit(), svelteBay()],
});
npm run dev).src/svelte-bay.d.ts file will be generated automatically.<Pod to="..."> and <Portal name="..."> will autocomplete with your portal names!If you prefer not to use the plugin, you can manually define your portal names in your src/app.d.ts:
// src/app.d.ts
import "svelte-bay";
declare module "svelte-bay" {
interface PortalRegistry {
header: boolean;
sidebar: boolean;
}
}
MIT