App Shell
The app's top-level page shell: a header band across the top, then a nav column beside the page content. The page itself is what scrolls — the header is sticky, so it holds while content travels under its translucent, blurred backing, and the light-seam along its bottom edge is the app's light source (see .surface-sheen). lightSource aims it at the pointer through --light-x, so the hot spot slides along the seam and every surface's wash swings with it. The header carries the brand lockup — Brand unless logo replaces it — plus (at md and up) search, DisplayControls and the account menu; PageHeader still owns page-level actions underneath it. With nav, a Sidebar of labelled entries stands to the left of main at md and up — always expanded, never collapsed to icons, since a label read at a glance beats a tooltip. It sits straight on the page background with no surface of its own, because navigation is not content and only main's cards should read as panels. It sticks below the header and keeps the leftover viewport height, so a long list scrolls inside the column rather than dragging the page. Header, nav and main are all capped at --shell-max-w (1650px) and centered together by default, so on wide screens the shell hugs the content instead of the viewport edge. Set contained={false} to drop the cap for pages whose content wants the full viewport instead. Below md, search and the account menu move instead into the same Sidebar inside a drawer opened from a hamburger in the header — search on top, the nav list filling the middle, UserMenu and DisplayControls pinned to the bottom. Use once per app, wrapping the routed page content in children.
What it looks like
One per app, wrapping the routed page. The header is sticky and translucent, so content travels under it, and the light-seam along its bottom edge is the app's light source — lightSource aims it at the pointer, and every surface's wash swings with it. This site is inside one right now: the header, the sidebar and the search control above are all AppShell. Everything below is a still frame of the same thing, so the parts can be pointed at.
Dashboard
Today across every channel.
Revenue
<div class="bg-background overflow-hidden rounded-lg">
<div class="bg-background/50 flex h-14 items-center gap-3 px-4 backdrop-blur-xl">
<span class="text-sm font-medium"
><span class="font-josefin">firmly.</span> / Admin</span
>
<div class="ml-auto flex items-center gap-3">
<Input class="h-8 w-40" placeholder="Search" />
<span class="text-muted text-xs">controls · account</span>
</div>
</div>
<div class="light-seam h-px"></div>
<div class="flex">
<nav class="w-44 shrink-0 space-y-1 p-3">
{#each nav[0].items as item (item.href)}
<span
class={[
'flex h-8 items-center gap-2 rounded-md px-2 text-xs',
item.label === 'Dashboard'
? 'bg-selected text-selected-foreground font-medium'
: 'text-foreground'
]}
>
<item.icon class="size-3.5" />
{item.label}
</span>
{/each}
</nav>
<div class="min-w-0 flex-1 p-4">
<PageHeader
title="Dashboard"
description="Today across every channel."
class="mb-4!"
/>
<Card>
<CardHeader>
<CardTitle>Revenue</CardTitle>
</CardHeader>
</Card>
</div>
</div>
</div>A product with a mark of its own
wordmark covers a product that renames the lockup; the logo snippet replaces it outright, for one that draws its own. This site is the case: the header above renders ground's mark instead of Brand. Leave it unset and every app on the platform keeps firmly., which is the point — an app never decides what the platform's brand looks like.
<div class="bg-background overflow-hidden rounded-lg">
<div class="bg-background/50 flex h-14 items-center px-4 backdrop-blur-xl">
<GroundEmblem class="text-primary size-9" />
</div>
<div class="light-seam h-px"></div>
</div>How an app mounts it
nav is the only thing that decides whether there is a sidebar at all — omit it, or pass mode="embedded", and the shell renders the header and content alone, which is what an app hosted inside another shell wants. The search snippet is rendered in the header at md and up, and at the top of the mobile drawer below that. theme comes from createTheme() and is forwarded to DisplayControls; the text scale is owned by the shell itself, since nothing outside it reads the level back.
A real mount is a handful of lines in the root layout — see this app's own src/routes/+layout.svelte. Everything else on a page comes from PageHeader downwards.
<div class="text-muted max-w-xl text-sm">
<p>
A real mount is a handful of lines in the root layout — see this app's own
<code>src/routes/+layout.svelte</code>. Everything else on a page comes from
<code>PageHeader</code> downwards.
</p>
<div class="mt-3">
<Button href="/docs/getting-started" variant="secondary">Getting started</Button>
</div>
</div>API
| Prop | Type | Default | Description |
|---|---|---|---|
user | {email: string, name?: string} | Signed-in user, forwarded to | |
theme | {resolved: 'light'|'dark', toggle: () => void} | Theme controller, forwarded to | |
appName | string | Child app name rendered in the | |
wordmark | string | Forwarded to | |
env | string | Environment name shown as a chip, hidden in production. | |
beta | boolean | false | Marks the app as pre-release with a |
homeHref | string | '/' | Route treated as the app root, forwarded to |
logoHref | string | '/' | Href for the logo. |
nav | import('../sidebar/sidebar.svelte').NavGroup[] | [] | Navigation groups. Omit to render the shell without navigation. |
sections | Array<{href: string, label: string, current?: boolean}> | [] | The app's top-level areas, rendered as a row in the header and at the top of the mobile drawer. Reach for it when one |
onLogout | () => void | Forwarded to | |
mode | 'standalone'|'embedded' | 'standalone' |
|
contained | boolean | true | Caps the header and content at |
logo | import('svelte').Snippet | Replaces the default | |
search | import('svelte').Snippet | Quick-access control rendered in the header at | |
navTop | import('svelte').Snippet | Rendered above the nav groups in | |
children | import('svelte').Snippet | Routed page content rendered in the scrollable main area. |