Layout Components
The app shell is assembled in the (app)/+layout.svelte route using the shadcn-svelte Sidebar provider pattern. Layout components live in $lib/modules/layout/.
Shell Architecture
Section titled “Shell Architecture”The top-level layout uses a three-column structure wrapped in a Sidebar.Provider:
<!-- src/routes/(app)/+layout.svelte --><Sidebar.Provider> <!-- Left Sidebar --> <AppSidebar user={data.user} profile={data.profile} installedApps={data.installedApps} />
<!-- Center: header + content --> <Sidebar.Inset> <SiteHeader unreadNotifications={data.unreadNotifications} /> <div class="mx-auto flex w-full max-w-4xl flex-1 flex-col pb-14 sm:pb-0"> {@render children()} </div> </Sidebar.Inset>
<!-- Right Sidebar --> <SidebarRight> <SidebarSuggestions /> <SidebarAd /> {#snippet footer()} <SidebarFooter /> {/snippet} </SidebarRight></Sidebar.Provider>
<MobileNav />AppSidebar (Left)
Section titled “AppSidebar (Left)”Import: $lib/modules/layout
The primary navigation sidebar. Uses collapsible="offcanvas" so it slides off-screen on mobile rather than collapsing to icons.
| Prop | Type | Description |
|---|---|---|
user | User | null | Supabase auth user object |
profile | Profile | null | User profile with name, avatar, role, type |
installedApps | InstalledApp[] | Apps installed by the user (shown in workspace section) |
Internal Structure
Section titled “Internal Structure”The sidebar is composed of several navigation sections:
AppSidebar├── Header → Logo linking to /feed├── Content│ ├── NavMain → Primary nav (Home, Explore, Messages, Profile, Honeycomb, Website Builder)│ ├── NavWorkspace → Business apps (dynamic, based on installedApps)│ └── NavSecondary → Bottom nav (Marketplace, Jobs, Earnings)└── Footer └── NavUser → User avatar, name, dropdown with Settings/Account/NotificationsNavigation Config
Section titled “Navigation Config”All navigation items are defined in $lib/modules/layout/config.ts:
import HomeIcon from "@tabler/icons-svelte/icons/home";// ...
export const navMain = [ { title: "Home", url: "/feed", icon: HomeIcon }, { title: "Explore", url: "/explore", icon: CompassIcon }, { title: "Messages", url: "/messages", icon: MessageCircleIcon }, { title: "Profile", url: "/profile", icon: UserCircleIcon }, { title: "Honeycomb", url: "/honeycomb", icon: HexagonIcon }, { title: "Website Builder", url: "/business/apps/sitebuilder", icon: BrowserIcon },];
export const navSecondary = [ { title: "Marketplace", url: "/marketplace", icon: BuildingStoreIcon }, { title: "Jobs", url: "/jobs", icon: BriefcaseIcon }, { title: "Earnings", url: "/wallet", icon: WalletIcon },];Active state detection uses the isNavItemActive helper, which checks for exact matches or nested path segments (preventing /feed from matching /feedback):
export function isNavItemActive(pathname: string, href: string): boolean { return pathname === href || pathname.startsWith(`${href}/`);}SiteHeader
Section titled “SiteHeader”Import: $lib/modules/layout
A sticky header bar that sits at the top of the center content area. Contains the sidebar trigger, auto-breadcrumb, and action buttons.
| Prop | Type | Default | Description |
|---|---|---|---|
unreadNotifications | number | 0 | Badge count for the notification bell |
Features
Section titled “Features”- Sidebar.Trigger — toggles the left sidebar open/closed
- AutoBreadcrumb — automatically generates breadcrumbs from the current URL
- Search button — opens the command palette
- NotificationBell — shows unread notification count
- Bookmarks link — navigates to
/bookmarks - Theme toggle — switches between light and dark mode via
mode-watcher
The header uses a frosted glass effect with backdrop-blur-sm and is sticky at the top of the viewport (sticky top-0 z-10).
SidebarRight
Section titled “SidebarRight”Import: $lib/modules/layout
The right sidebar holds contextual content such as suggested connections, ads, and footer links. It accepts children for the main content area and a footer snippet for the bottom section.
Composition pattern
Section titled “Composition pattern”<SidebarRight> <SidebarSuggestions /> <SidebarAd /> {#snippet footer()} <SidebarFooter /> {/snippet}</SidebarRight>Sub-components available from $lib/modules/layout:
SidebarSuggestions— suggested profiles/contentSidebarAd— promotional content areaSidebarSearch— search widgetSidebarFooter— footer links and copyright
MobileNav
Section titled “MobileNav”Import: $lib/modules/shared
A fixed bottom navigation bar visible only on mobile (md:hidden). Renders outside the sidebar provider.
Navigation items
Section titled “Navigation items”Defined in $lib/modules/layout/config.ts as mobileNav:
| Item | Route | Icon |
|---|---|---|
| Home | /feed | HomeIcon |
| Explore | /explore | CompassIcon |
| Messages | /messages | MessageCircleIcon |
| Notifications | /notifications | BellIcon |
| Profile | /profile | UserCircleIcon |
The active item is highlighted using the same isNavItemActive helper as the desktop sidebar.
Breadcrumb System
Section titled “Breadcrumb System”The breadcrumb system automatically derives navigation crumbs from the current URL pathname. No manual configuration is needed per page.
How it works
Section titled “How it works”breadcrumbsFromPathname(pathname)splits the URL into segments and capitalizes each one.splitCrumbsForEllipsis(crumbs, maxBeforeEllipsis)collapses middle segments into a dropdown when the path is deeply nested (default threshold: 4 segments).AutoBreadcrumbrenders the result using theBreadcrumbUI primitives and aDropdownMenufor the ellipsis.
Example
Section titled “Example”For the URL /business/apps/courses/123/edit:
Business > ... > 123 > Edit │ └── Dropdown: Apps, CoursesCustomizing labels
Section titled “Customizing labels”The formatSegment function applies these transformations:
- URL-decodes the segment
- Replaces hyphens and underscores with spaces
- Capitalizes each word
If you need custom labels for specific routes, override them in your page component by providing your own breadcrumb data rather than relying on the auto-generated version.
API Reference
Section titled “API Reference”type BreadcrumbCrumb = { label: string; /** null = current page (rendered as Breadcrumb.Page) */ href: string | null;};
function breadcrumbsFromPathname(pathname: string): BreadcrumbCrumb[];
function splitCrumbsForEllipsis( crumbs: BreadcrumbCrumb[], maxBeforeEllipsis?: number, // default: 4): { head: BreadcrumbCrumb; middle: BreadcrumbCrumb[]; tail: BreadcrumbCrumb[] } | null;