Skip to content

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/.

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 />

Import: $lib/modules/layout

The primary navigation sidebar. Uses collapsible="offcanvas" so it slides off-screen on mobile rather than collapsing to icons.

PropTypeDescription
userUser | nullSupabase auth user object
profileProfile | nullUser profile with name, avatar, role, type
installedAppsInstalledApp[]Apps installed by the user (shown in workspace section)

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/Notifications

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}/`);
}

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.

PropTypeDefaultDescription
unreadNotificationsnumber0Badge count for the notification bell
  • 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).

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.

<SidebarRight>
<SidebarSuggestions />
<SidebarAd />
{#snippet footer()}
<SidebarFooter />
{/snippet}
</SidebarRight>

Sub-components available from $lib/modules/layout:

  • SidebarSuggestions — suggested profiles/content
  • SidebarAd — promotional content area
  • SidebarSearch — search widget
  • SidebarFooter — footer links and copyright

Import: $lib/modules/shared

A fixed bottom navigation bar visible only on mobile (md:hidden). Renders outside the sidebar provider.

Defined in $lib/modules/layout/config.ts as mobileNav:

ItemRouteIcon
Home/feedHomeIcon
Explore/exploreCompassIcon
Messages/messagesMessageCircleIcon
Notifications/notificationsBellIcon
Profile/profileUserCircleIcon

The active item is highlighted using the same isNavItemActive helper as the desktop sidebar.

The breadcrumb system automatically derives navigation crumbs from the current URL pathname. No manual configuration is needed per page.

  1. breadcrumbsFromPathname(pathname) splits the URL into segments and capitalizes each one.
  2. splitCrumbsForEllipsis(crumbs, maxBeforeEllipsis) collapses middle segments into a dropdown when the path is deeply nested (default threshold: 4 segments).
  3. AutoBreadcrumb renders the result using the Breadcrumb UI primitives and a DropdownMenu for the ellipsis.

For the URL /business/apps/courses/123/edit:

Business > ... > 123 > Edit
└── Dropdown: Apps, Courses

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.

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;