@sveltopia/regions
v0.1.1
Published
Layout regions system for passing content from pages to layouts in SvelteKit
Maintainers
Readme
@sveltopia/regions
Pass content from child pages to parent layouts in SvelteKit. Server-rendered data or reactive snippets. Type-safe with optional validation.
Svelte 5 ready.
Features
Pass page-specific content (headers, breadcrumbs, sidebars) from pages to parent layouts without prop drilling.
Three Strategies:
- Load Functions - SSR-friendly data (perfect for SEO)
- Component Wrappers - Simple client-side props
- Snippets - Full reactive UI with page state
Key Features:
- SSR-friendly with zero layout shift
- Type-safe with TypeScript + optional runtime validation
- No prop drilling - pages set regions directly
- Mix strategies on the same page
- Zero runtime cost (validation stripped in production)
Installation
npm install @sveltopia/regions
# or
pnpm add @sveltopia/regions
# or
yarn add @sveltopia/regionsRequirements: SvelteKit 2.12.0+, Svelte 5.0.0+
Quick Start (CLI)
Generate everything with one command:
npx @sveltopia/regions add page-headerThe CLI walks you through:
- Strategy choice - Load Function / Component Wrapper / Snippet
- Field definitions - What data this region needs
- Validator - Valibot / Zod / TypeScript-only
- File generation - Schema, components, and examples
Generated files are production-ready with full TypeScript support.
Example Output (Load Function Strategy)
✔ Which strategy? › Load function (SSR, best for SEO)
✔ Fields? › title, description
✔ Validator? › Valibot
Generated:
src/lib/regions/page-header/pageHeaderSchema.ts
src/lib/regions/page-header/PageHeaderRegion.svelte
src/routes/page-header-example/+page.server.ts
src/routes/page-header-example/+page.svelteSee Full Documentation for all strategies and advanced usage.
1. Define Types & Layout
// lib/types.ts
export interface HeaderData {
title: string;
description?: string;
}<!-- routes/+layout.svelte -->
<script lang="ts">
import { LayoutRegions, LayoutRegion } from '@sveltopia/regions';
import type { HeaderData } from '$lib/types';
</script>
<LayoutRegions>
<LayoutRegion name="header">
{#snippet children(data)}
{#if data}
{@const typed = data as HeaderData}
<h1>{typed.title}</h1>
{#if typed.description}<p>{typed.description}</p>{/if}
{/if}
{/snippet}
{#snippet fallback()}
<h1>Default Title</h1>
{/snippet}
</LayoutRegion>
{@render children?.()}
</LayoutRegions>2. Set Content from Pages
Load Function Strategy:
// routes/about/+page.server.ts
import type { HeaderData } from '$lib/types';
export const load = () => ({
regions: {
header: {
title: "About",
description: "Learn more"
} satisfies HeaderData
}
});Component Wrapper Strategy:
<!-- routes/about/+page.svelte -->
<script lang="ts">
import { useLayoutRegions } from '@sveltopia/regions';
import type { HeaderData } from '$lib/types';
useLayoutRegions({
header: {
title: "About",
description: "Learn more"
} satisfies HeaderData
});
</script>Snippet Strategy:
<!-- routes/about/+page.svelte -->
<script lang="ts">
import { useLayoutRegions } from '@sveltopia/regions';
useLayoutRegions({ header: headerSnippet });
</script>
{#snippet headerSnippet()}
<h1>About</h1>
<p>Learn more</p>
{/snippet}Core Concepts
Three-State Logic
Regions support three states for flexible content control:
// 1. Content - renders the provided data/snippet
useLayoutRegions({ header: { title: "Products" } });
// 2. Undefined - renders fallback if defined
useLayoutRegions({ /* header not mentioned */ });
// 3. Null - renders nothing (suppresses fallback)
useLayoutRegions({ header: null });Schema Validation (Optional)
Add runtime validation with Valibot or Zod:
Valibot:
// lib/regions/headerSchema.ts
import * as v from 'valibot';
import { valibot } from '@sveltopia/regions';
const _headerSchema = v.pipe(
v.object({
title: v.string(),
category: v.union([
v.literal('components'),
v.literal('patterns')
])
})
);
export type HeaderData = v.InferOutput<typeof _headerSchema>;
export const headerSchema = valibot<HeaderData>(_headerSchema, v.parse);Zod:
// lib/regions/headerSchema.ts
import { z } from 'zod';
import type { RegionSchema } from '@sveltopia/regions';
const _headerSchema = z.object({
title: z.string(),
category: z.enum(['components', 'patterns'])
});
export type HeaderData = z.infer<typeof _headerSchema>;
export const headerSchema = _headerSchema satisfies RegionSchema<HeaderData>;Usage in layout:
<script lang="ts">
import { headerSchema, type HeaderData } from '$lib/regions/headerSchema';
</script>
<LayoutRegion name="header" schema={headerSchema}>
{#snippet children(data)}
{@const typed = data as HeaderData}
<h1>{typed.title}</h1>
{/snippet}
</LayoutRegion>Benefits: Catches typos in enums/literals, validates external data, dev-only (zero production cost).
TypeScript-Only (No Runtime Validation)
If you don't need runtime validation, use TypeScript interfaces for type safety:
Define interface:
// lib/types.ts
export interface PageHeaderData {
title: string;
description?: string;
}Layout with type assertion:
<script lang="ts">
import { LayoutRegions, LayoutRegion } from '@sveltopia/regions';
import type { PageHeaderData } from '$lib/types';
</script>
<LayoutRegions>
<LayoutRegion name="page-header">
{#snippet children(data)}
{#if data}
{@const typed = data as PageHeaderData}
<h1>{typed.title}</h1>
{#if typed.description}<p>{typed.description}</p>{/if}
{/if}
{/snippet}
</LayoutRegion>
{@render children?.()}
</LayoutRegions>Load function with type checking:
// routes/about/+page.server.ts
import type { PageHeaderData } from '$lib/types';
export const load = () => ({
regions: {
'page-header': {
title: "About",
description: "Learn more"
} satisfies PageHeaderData
}
});Benefits: Compile-time type safety, zero runtime cost, no dependencies. Trade-off: No validation of data at runtime (typos in enum values won't be caught).
API Reference
Components
<LayoutRegions>
Wraps layouts to enable regions.
Props: children?, schemas?, warnings?
<LayoutRegion name>
Renders a specific region.
Props: name (required), children?, fallback?, schema?, required?
Functions
useLayoutRegions(options)
Hook for pages to set regions.
useLayoutRegions({
header: { title: "Products" }, // Data
sidebar: sidebarSnippet, // Snippet
footer: null // Suppress
});Returns LayoutRegionsContext for imperative updates:
const ctx = useLayoutRegions({ ... });
ctx?.setRegion('header', { title: 'New Title' });
ctx?.clearRegion('header');Types
import type {
LayoutRegionsOptions,
LayoutRegionsContext,
RegionSchema,
RegionSchemas,
RegionContent,
WarningsConfig
} from '@sveltopia/regions';Development Warnings
In development mode, the library provides helpful warnings to catch common mistakes:
Unexpected Region (enabled by default) - Fires immediately when a page sets a region that no layout defines:
[regions] Unexpected region "typoSidebar"
The page is setting a region that no layout is using.
Check that <LayoutRegion name="typoSidebar" /> exists in your layout.Missing Required Region (enabled by default) - Fires when a page doesn't set a region marked as required:
<LayoutRegion name="header" required>
...
</LayoutRegion>Unused Region (disabled by default) - Fires when a region is defined in the layout but no page sets it. Disabled by default because "catch-all" layouts that define all possible regions are a common valid pattern.
Configuring Warnings
<!-- Enable strict checking (including unused regions) -->
<LayoutRegions warnings={{ unused: true }}>
...
</LayoutRegions><!-- Disable all warnings -->
<LayoutRegions warnings={false}>
...
</LayoutRegions>Warnings have zero runtime cost in production (only run when dev === true).
Comparison with Alternatives
Compare @sveltopia/regions against other SvelteKit patterns for passing content between pages and layouts:
| Feature | @sveltopia/regions | Props (↓) | Context/Stores | Manual Implementation | | ---------------- | ---------------------------- | --------- | -------------- | --------------------- | | Data Flow | Page → Layout (↑) | Layout → Page (↓) | Bidirectional | Page → Layout (↑) | | SSR-Friendly | Yes (with load functions) | Yes | No | Yes (with work) | | No Prop Drilling | Yes | No | Yes | No | | Type Safe | Yes | Yes | Partial | Yes (with work) | | Dynamic Content | Yes | Partial | Yes | Yes (with work) | | Validation | Yes | Partial | No | No |
Explanation:
- @sveltopia/regions: Purpose-built for passing content FROM pages UP to parent layouts with three strategies and optional validation
- Props (↓): Traditional component props - only work in opposite direction (layout → page), not suitable for page headers/breadcrumbs
- Context/Stores: Can pass data upward, but not SSR-friendly and requires manual context setup in every layout/page
- Manual Implementation: Build your own context-based system - flexible but requires significant boilerplate and maintenance
Contributing
We welcome contributions! Here's how to get started:
Development Setup:
git clone https://github.com/sveltopia/regions.git
cd regions
pnpm install
pnpm test # Run tests
pnpm build # Build packageBefore submitting a PR:
- Run tests:
pnpm test - Ensure types check:
pnpm check - Follow the existing code style
See CONTRIBUTING.md for detailed guidelines.
Support
- GitHub Issues - Bug reports and feature requests
- Full Documentation - Comprehensive guides and examples
Changelog
See CHANGELOG.md for version history and migration guides.
License
MIT © Sveltopia
See LICENSE for details.
