# Application layout

Compose one application shell with navigation and independently scrolling content.

## Start at the application boundary

[DashboardShell](/docs/components/dashboard-shell/) coordinates Sidebar, header, and content layout. Use [Sidebar](/docs/components/sidebar/) directly when you need its navigation without the shell composition. Neither component implements routing or authentication. The example sets `layout="inset"` for a framed content surface; the default `layout="push"` runs the content on one unframed surface from the sidebar to the edge.

Workspace dashboard shell

[Complete TSX file](/examples/dashboard-shell/inset.tsx)

```tsx
"use client";

import {
  DashboardShell,
  DashboardShellHeader,
  DashboardShellInset,
  type DashboardShellLayout,
  DashboardShellMain,
  DashboardShellPrimary,
  DashboardShellSidebar,
  DashboardShellSidebarHeader,
  DashboardShellSidebarTrigger,
} from "@/siteplane/components/dashboard-shell";
import {
  Breadcrumb,
  BreadcrumbItem,
  BreadcrumbList,
  BreadcrumbPage,
  BreadcrumbSeparator,
} from "@/siteplane/components/breadcrumb";
import {
  ArrowUpRightIcon,
  BookMarkedIcon,
  BookOpenIcon,
  ChevronDownIcon,
  ChevronsUpDownIcon,
  CircleDotIcon,
  CircleHelpIcon,
  CopyIcon,
  FolderInputIcon,
  FolderPlusIcon,
  HeadsetIcon,
  HouseIcon,
  LayersIcon,
  LibraryIcon,
  LifeBuoyIcon,
  LogOutIcon,
  PencilIcon,
  PlusIcon,
  SettingsIcon,
  StarOffIcon,
  SunMoonIcon,
  Trash2Icon,
  UserIcon,
  UsersIcon,
} from "lucide-react";
import * as React from "react";

import { Avatar, AvatarFallback } from "@/siteplane/components/avatar";
import { Button } from "@/siteplane/components/button";
import { Card, CardPanel } from "@/siteplane/components/card";
import {
  Collapsible,
  CollapsiblePanel,
  CollapsibleTrigger,
} from "@/siteplane/components/collapsible";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuRadioGroup,
  DropdownMenuRadioItem,
  DropdownMenuSeparator,
  DropdownMenuShortcut,
  DropdownMenuSub,
  DropdownMenuSubContent,
  DropdownMenuSubTrigger,
  DropdownMenuTrigger,
} from "@/siteplane/components/dropdown-menu";
import { Kbd } from "@/siteplane/components/kbd";
import {
  Tooltip,
  TooltipPopup,
  TooltipTrigger,
} from "@/siteplane/components/tooltip";
import {
  SidebarBrand,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarGroupLabel,
  SidebarHeader,
  SidebarMenu,
  SidebarMenuAction,
  SidebarMenuButton,
  SidebarMenuButtonDescription,
  SidebarMenuButtonText,
  SidebarMenuButtonTitle,
  SidebarMenuItem,
  SidebarMenuSub,
  SidebarMenuSubButton,
  SidebarMenuSubItem,
  SidebarRail,
  SidebarTrigger,
  sidebarMenuPopupWidthClassName,
  useSidebar,
} from "@/siteplane/components/sidebar";

type SidebarIconPath = string | readonly string[];

function SidebarIcon({
  iconPath,
  ...props
}: React.ComponentProps<"svg"> & {
  iconPath: SidebarIconPath;
}): React.ReactElement {
  const paths = typeof iconPath === "string" ? [iconPath] : iconPath;

  return (
    <svg aria-hidden="true" fill="currentColor" viewBox="0 0 24 24" {...props}>
      {paths.map((value) => (
        <path d={value} fill="currentColor" key={value} />
      ))}
    </svg>
  );
}

const sidebarIconPaths = {
  connectors:
    "M19.25 7C19.25 5.75736 18.2426 4.75 17 4.75C15.7574 4.75 14.75 5.75736 14.75 7C14.75 8.24264 15.7574 9.25 17 9.25C18.2426 9.25 19.25 8.24264 19.25 7ZM9.25 7C9.25 5.75736 8.24264 4.75 7 4.75C5.75736 4.75 4.75 5.75736 4.75 7C4.75 8.24264 5.75736 9.25 7 9.25C8.24264 9.25 9.25 8.24264 9.25 7ZM4.75 17C4.75 18.2426 5.75736 19.25 7 19.25C8.24264 19.25 9.25 18.2426 9.25 17C9.25 15.7574 8.24264 14.75 7 14.75C5.75736 14.75 4.75 15.7574 4.75 17ZM10.75 7C10.75 9.07107 9.07107 10.75 7 10.75C4.92893 10.75 3.25 9.07107 3.25 7C3.25 4.92893 4.92893 3.25 7 3.25C9.07107 3.25 10.75 4.92893 10.75 7ZM20.75 7C20.75 8.81421 19.4617 10.3273 17.75 10.6748V14C17.75 16.0711 16.0711 17.75 14 17.75H10.6748C10.3273 19.4617 8.81421 20.75 7 20.75C4.92893 20.75 3.25 19.0711 3.25 17C3.25 14.9289 4.92893 13.25 7 13.25C8.81421 13.25 10.3273 14.5383 10.6748 16.25H14C15.2426 16.25 16.25 15.2426 16.25 14V10.6748C14.5383 10.3273 13.25 8.81421 13.25 7C13.25 4.92893 14.9289 3.25 17 3.25C19.0711 3.25 20.75 4.92893 20.75 7Z",
  dashboard:
    "M18.2499 8.93869L13.3173 5.37717C12.5309 4.8092 11.469 4.8092 10.6825 5.37717L5.74992 8.93869V17.0002C5.74993 18.2429 6.75728 19.2502 7.99992 19.2502H15.9999C17.2426 19.2502 18.2499 18.2429 18.2499 17.0002V8.93869ZM14.9999 15.2502C15.4141 15.2502 15.7499 15.586 15.7499 16.0002C15.7499 16.4144 15.4141 16.7502 14.9999 16.7502H8.99992C8.58571 16.7502 8.24992 16.4144 8.24992 16.0002C8.24992 15.586 8.58571 15.2502 8.99992 15.2502H14.9999ZM19.7499 17.0002C19.7499 19.0713 18.071 20.7502 15.9999 20.7502H7.99992C5.92886 20.7502 4.24993 19.0713 4.24992 17.0002V10.0227L3.43938 10.6086C3.1036 10.8511 2.63405 10.7754 2.39153 10.4397C2.14902 10.1039 2.22471 9.63434 2.56047 9.39181L9.80461 4.16037C11.1151 3.21412 12.8847 3.21412 14.1952 4.16037L21.4394 9.39181C21.7751 9.63434 21.8508 10.1039 21.6083 10.4397C21.3658 10.7754 20.8963 10.8511 20.5605 10.6086L19.7499 10.0227V17.0002Z",
  ellipsis:
    "M6.75 12C6.75 12.9665 5.9665 13.75 5 13.75C4.0335 13.75 3.25 12.9665 3.25 12C3.25 11.0335 4.0335 10.25 5 10.25C5.9665 10.25 6.75 11.0335 6.75 12ZM13.75 12C13.75 12.9665 12.9665 13.75 12 13.75C11.0335 13.75 10.25 12.9665 10.25 12C10.25 11.0335 11.0335 10.25 12 10.25C12.9665 10.25 13.75 11.0335 13.75 12ZM20.75 12C20.75 12.9665 19.9665 13.75 19 13.75C18.0335 13.75 17.25 12.9665 17.25 12C17.25 11.0335 18.0335 10.25 19 10.25C19.9665 10.25 20.75 11.0335 20.75 12Z",
  inbox:
    "M20.25 12.75H15.6914C15.3847 14.4557 13.8935 15.75 12.0996 15.75H11.9004C10.1065 15.75 8.61526 14.4557 8.30859 12.75H3.75V16C3.75 17.2426 4.75736 18.25 6 18.25H18C19.2426 18.25 20.25 17.2426 20.25 16V12.75ZM7.88477 5.75C7.02064 5.75014 6.23237 6.24489 5.85742 7.02344L3.97266 10.9395C3.92435 11.0398 3.88631 11.1442 3.85352 11.25H9C9.41421 11.25 9.75 11.5858 9.75 12V12.0996C9.75 13.287 10.713 14.25 11.9004 14.25H12.0996C13.287 14.25 14.25 13.287 14.25 12.0996V12C14.25 11.5858 14.5858 11.25 15 11.25H20.1426C20.1105 11.1485 20.0747 11.0477 20.0283 10.9512L18.1416 7.02539C17.767 6.24579 16.9782 5.75001 16.1133 5.75H7.88477ZM21.75 16C21.75 18.0711 20.0711 19.75 18 19.75H6C3.92893 19.75 2.25 18.0711 2.25 16V11.915C2.25001 11.3518 2.37673 10.7956 2.62109 10.2881L4.50684 6.37305C5.13178 5.07536 6.44445 4.25014 7.88477 4.25H16.1133C17.5549 4.25001 18.8688 5.07664 19.4932 6.37598L21.3799 10.3018C21.6234 10.8085 21.7499 11.3636 21.75 11.9258V16Z",
  projects:
    "M9.25 15.9998C9.24992 15.3095 8.6903 14.7498 8 14.7498H6C5.30969 14.7498 4.75008 15.3095 4.75 15.9998V17.9998C4.75 18.6901 5.30964 19.2498 6 19.2498H8C8.69036 19.2498 9.25 18.6901 9.25 17.9998V15.9998ZM19.25 15.9998C19.2499 15.3095 18.6903 14.7498 18 14.7498H16C15.3097 14.7498 14.7501 15.3095 14.75 15.9998V17.9998C14.75 18.6901 15.3096 19.2498 16 19.2498H18C18.6904 19.2498 19.25 18.6901 19.25 17.9998V15.9998ZM15.0557 3.64139C16.1296 2.56745 17.8704 2.56745 18.9443 3.64139L20.3584 5.05546C21.4323 6.1294 21.4323 7.87019 20.3584 8.94413L18.9443 10.3592C17.8705 11.4327 16.1295 11.4327 15.0557 10.3592L13.6416 8.94413C12.5677 7.87019 12.5677 6.1294 13.6416 5.05546L15.0557 3.64139ZM9.25 5.99979C9.24992 5.30951 8.69031 4.74979 8 4.74979H6C5.30969 4.74979 4.75008 5.30951 4.75 5.99979V7.99979C4.75 8.69015 5.30964 9.24979 6 9.24979H8C8.69036 9.24979 9.25 8.69015 9.25 7.99979V5.99979ZM17.8838 4.70194C17.3956 4.21379 16.6044 4.21379 16.1162 4.70194L14.7021 6.116C14.214 6.60416 14.214 7.39543 14.7021 7.88358L16.1162 9.29764C16.6044 9.7858 17.3956 9.7858 17.8838 9.29764L19.2979 7.88358C19.786 7.39543 19.786 6.60416 19.2979 6.116L17.8838 4.70194ZM10.75 17.9998C10.75 19.5186 9.51878 20.7498 8 20.7498H6C4.48122 20.7498 3.25 19.5186 3.25 17.9998V15.9998C3.25008 14.4811 4.48127 13.2498 6 13.2498H8C9.51873 13.2498 10.7499 14.4811 10.75 15.9998V17.9998ZM20.75 17.9998C20.75 19.5186 19.5188 20.7498 18 20.7498H16C14.4812 20.7498 13.25 19.5186 13.25 17.9998V15.9998C13.2501 14.4811 14.4813 13.2498 16 13.2498H18C19.5187 13.2498 20.7499 14.4811 20.75 15.9998V17.9998ZM10.75 7.99979C10.75 9.51858 9.51878 10.7498 8 10.7498H6C4.48122 10.7498 3.25 9.51858 3.25 7.99979V5.99979C3.25008 4.48108 4.48127 3.24979 6 3.24979H8C9.51873 3.24979 10.7499 4.48108 10.75 5.99979V7.99979Z",
  resources:
    "M20.25 12C20.25 7.44365 16.5563 3.75 12 3.75C7.44365 3.75 3.75 7.44365 3.75 12C3.75 16.5563 7.44365 20.25 12 20.25C16.5563 20.25 20.25 16.5563 20.25 12ZM14.8525 8.26465C15.0984 8.21547 15.353 8.29241 15.5303 8.46973C15.7076 8.64704 15.7845 8.90157 15.7354 9.14746L14.7354 14.1475C14.6759 14.4441 14.4441 14.6759 14.1475 14.7354L9.14746 15.7354C8.90157 15.7845 8.64704 15.7076 8.46973 15.5303C8.29241 15.353 8.21547 15.0984 8.26465 14.8525L9.26465 9.85254L9.29492 9.74512C9.3831 9.50101 9.59286 9.31672 9.85254 9.26465L14.8525 8.26465ZM10.6367 10.6367L9.95605 14.0439L13.3623 13.3623L14.0439 9.95605L10.6367 10.6367ZM21.75 12C21.75 17.3848 17.3848 21.75 12 21.75C6.61522 21.75 2.25 17.3848 2.25 12C2.25 6.61522 6.61522 2.25 12 2.25C17.3848 2.25 21.75 6.61522 21.75 12Z",
  search:
    "M17.25 11a6.25 6.25 0 1 0-12.5 0 6.25 6.25 0 0 0 12.5 0m1.5 0c0 1.87-.663 3.585-1.766 4.924l4.046 4.046a.75.75 0 1 1-1.06 1.06l-4.046-4.046A7.75 7.75 0 1 1 18.75 11",
  shared:
    "M8.50032 12.25C8.91442 12.2501 9.25032 12.5859 9.25032 13C9.25032 13.4141 8.91442 13.7499 8.50032 13.75C6.62639 13.75 5.00405 14.8355 4.23079 16.415C4.07541 16.7328 4.13395 17.0174 4.34993 17.2666C4.58679 17.5398 5.01233 17.75 5.50032 17.75H7.50032C7.91442 17.7501 8.25032 18.0859 8.25032 18.5C8.25032 18.9141 7.91442 19.2499 7.50032 19.25H5.50032C4.6077 19.25 3.75725 18.8733 3.21614 18.249C2.65428 17.6008 2.43268 16.677 2.88411 15.7549C3.89957 13.6809 6.03215 12.25 8.50032 12.25ZM16.5003 12.25C19.17 12.2501 21.4473 13.9237 22.3431 16.2764C22.6501 17.0827 22.4285 17.8628 21.9417 18.4082C21.4702 18.9363 20.7541 19.2499 20.0003 19.25H13.0003C12.2464 19.25 11.5295 18.9365 11.0579 18.4082C10.5711 17.8628 10.3505 17.0828 10.6575 16.2764C11.5534 13.9237 13.8306 12.25 16.5003 12.25ZM16.5003 13.75C14.4734 13.75 12.7406 15.0203 12.0589 16.8105C11.9732 17.0362 12.0211 17.2344 12.1771 17.4092C12.3485 17.6012 12.6497 17.75 13.0003 17.75H20.0003C20.3507 17.7499 20.6512 17.601 20.8226 17.4092C20.9787 17.2343 21.0266 17.0363 20.9407 16.8105C20.2591 15.0204 18.5272 13.7501 16.5003 13.75ZM9.75032 8C9.75032 7.30972 9.19057 6.75013 8.50032 6.75C7.80996 6.75 7.25032 7.30964 7.25032 8C7.25032 8.69036 7.80996 9.25 8.50032 9.25C9.19057 9.24987 9.75032 8.69028 9.75032 8ZM18.2503 7.5C18.2503 6.53358 17.4667 5.75013 16.5003 5.75C15.5338 5.75 14.7503 6.5335 14.7503 7.5C14.7503 8.4665 15.5338 9.25 16.5003 9.25C17.4667 9.24987 18.2503 8.46642 18.2503 7.5ZM11.2503 8C11.2503 9.51871 10.019 10.7499 8.50032 10.75C6.98154 10.75 5.75032 9.51878 5.75032 8C5.75032 6.48122 6.98154 5.25 8.50032 5.25C10.019 5.25013 11.2503 6.48129 11.2503 8ZM19.7503 7.5C19.7503 9.29485 18.2951 10.7499 16.5003 10.75C14.7054 10.75 13.2503 9.29493 13.2503 7.5C13.2503 5.70507 14.7054 4.25 16.5003 4.25C18.2951 4.25013 19.7503 5.70515 19.7503 7.5Z",
  starred:
    "M12.0002 2.25C12.3031 2.25 12.5762 2.43225 12.6925 2.71191L14.8966 8.01172L20.6193 8.47168C20.9213 8.49589 21.179 8.69918 21.2726 8.9873C21.3661 9.27522 21.2777 9.591 21.048 9.78809L16.6877 13.5234L18.0197 19.1074C18.0898 19.402 17.9756 19.7097 17.7306 19.8877C17.4856 20.0656 17.158 20.0787 16.8996 19.9209L12.0002 16.9287L7.10074 19.9209C6.84228 20.0787 6.51473 20.0656 6.26968 19.8877C6.02466 19.7097 5.91046 19.402 5.98062 19.1074L7.31168 13.5234L2.9523 9.78809C2.72256 9.591 2.63418 9.27522 2.72769 8.9873C2.82131 8.69918 3.07903 8.49589 3.38101 8.47168L9.10269 8.01172L11.3078 2.71191L11.3586 2.6123C11.4929 2.38986 11.735 2.25 12.0002 2.25ZM10.3117 9.01172C10.2036 9.27112 9.95993 9.44813 9.67984 9.4707L5.298 9.82227L8.63589 12.6816C8.84949 12.8646 8.94334 13.1522 8.87808 13.4258L7.85757 17.7002L11.6095 15.4102L11.7023 15.3623C11.9241 15.2662 12.1809 15.282 12.3908 15.4102L16.1418 17.7002L15.1222 13.4258C15.057 13.1522 15.1508 12.8646 15.3644 12.6816L18.7013 9.82227L14.3205 9.4707C14.0404 9.44813 13.7967 9.27111 13.6886 9.01172L12.0002 4.95215L10.3117 9.01172Z",
  user: "M12.0006 12.25C15.523 12.2502 18.4949 14.5997 19.4381 17.8154C19.6746 18.6221 19.4321 19.3825 18.943 19.917C18.465 20.4392 17.7545 20.7499 17.0006 20.75H7.0006C6.24652 20.75 5.53531 20.4394 5.05724 19.917C4.56817 19.3826 4.32563 18.622 4.56212 17.8154C5.50533 14.5996 8.47805 12.25 12.0006 12.25ZM12.0006 13.75C9.16185 13.75 6.76251 15.6431 6.00158 18.2373C5.92731 18.4905 5.9923 18.717 6.16369 18.9043C6.34615 19.1037 6.65013 19.25 7.0006 19.25H17.0006C17.3509 19.2499 17.6541 19.1036 17.8365 18.9043C18.008 18.717 18.0729 18.4905 17.9986 18.2373C17.2377 15.6432 14.8392 13.7502 12.0006 13.75ZM14.2506 7C14.2506 5.75749 13.2431 4.75021 12.0006 4.75C10.758 4.75 9.7506 5.75736 9.7506 7C9.7506 8.24264 10.758 9.25 12.0006 9.25C13.2431 9.24979 14.2506 8.24251 14.2506 7ZM15.7506 7C15.7506 9.07094 14.0715 10.7498 12.0006 10.75C9.92953 10.75 8.2506 9.07107 8.2506 7C8.2506 4.92893 9.92953 3.25 12.0006 3.25C14.0715 3.25021 15.7506 4.92906 15.7506 7Z",
} as const;

type NavigationItem = {
  active?: boolean;
  href?: string;
  icon: SidebarIconPath;
  label: string;
  shortcut?: readonly string[];
};

const workspaces = [
  // A workspace with a logo shows it in its mark; one without shows its
  // initial.
  { icon: CircleDotIcon, initial: "A", name: "Acme", plan: "Pro plan" },
  { icon: null, initial: "L", name: "Acme Labs", plan: "Free plan" },
] as const;

const account = {
  avatar: "ME",
  email: "mara@example.com",
  name: "Mara Ellis",
};

const recentSpace = "Product roadmap";

const mainNavigation: NavigationItem[] = [
  {
    href: "#dashboard",
    icon: sidebarIconPaths.dashboard,
    label: "Dashboard",
  },
  {
    href: "#search",
    icon: sidebarIconPaths.search,
    label: "Search",
    shortcut: ["⌘", "K"],
  },
  {
    href: "#resources",
    icon: sidebarIconPaths.resources,
    label: "Resources",
  },
  {
    href: "#connectors",
    icon: sidebarIconPaths.connectors,
    label: "Connectors",
  },
];

const workspaceNavigation: NavigationItem[] = [
  {
    href: "#spaces",
    icon: sidebarIconPaths.projects,
    label: "All spaces",
  },
  { href: "#starred", icon: sidebarIconPaths.starred, label: "Starred" },
  {
    href: "#created-by-me",
    icon: sidebarIconPaths.user,
    label: "Created by me",
  },
  {
    href: "#shared-with-me",
    icon: sidebarIconPaths.shared,
    label: "Shared with me",
  },
];

type SidebarPatternLayout = "inset" | "push";

function ShellExample({
  layout,
}: {
  layout: DashboardShellLayout;
}): React.ReactElement {
  return (
    <div
      className="relative h-[640px] w-full overflow-hidden bg-[var(--sp-rw-bg-base)] [transform:translateZ(0)]"
      data-pattern="dashboard-shell-demo"
      data-shell-layout={layout}
    >
      <DashboardShell className="h-full min-h-0!" defaultOpen layout={layout}>
        <DashboardShellSidebar className="h-full!">
          <SidebarChrome Header={DashboardShellSidebarHeader} layout={layout} />
        </DashboardShellSidebar>

        <DashboardShellInset>
          <DashboardShellHeader>
            <DashboardShellSidebarTrigger
              aria-label="Toggle sidebar"
              className={layout === "inset" ? "md:hidden" : undefined}
            />
            <Breadcrumb>
              <BreadcrumbList>
                <BreadcrumbItem>Workspace</BreadcrumbItem>
                <BreadcrumbSeparator />
                <BreadcrumbItem>
                  <BreadcrumbPage>Dashboard</BreadcrumbPage>
                </BreadcrumbItem>
              </BreadcrumbList>
            </Breadcrumb>
          </DashboardShellHeader>
          <DashboardShellPrimary aria-label="Workspace">
            <DashboardShellMain className="flex flex-col gap-5">
              <div>
                <h1 className="font-medium text-xl">Welcome back</h1>
                <p className="mt-1 text-muted-foreground text-sm">
                  Here is what changed in your Acme workspace this week.
                </p>
              </div>
              <Card className="h-28" />
              <div className="grid gap-4 sm:grid-cols-3">
                <Card className="h-32" />
                <Card className="h-32" />
                <Card className="h-32" />
              </div>
            </DashboardShellMain>
          </DashboardShellPrimary>
        </DashboardShellInset>
      </DashboardShell>
    </div>
  );
}

export default function Example() {
  return <ShellExample layout="inset" />;
}

type SidebarHeaderSlot = React.ComponentType<
  React.ComponentProps<typeof SidebarHeader>
>;

function SidebarChrome({
  Header = SidebarHeader,
  layout = "inset",
}: {
  Header?: SidebarHeaderSlot;
  layout?: SidebarPatternLayout;
} = {}): React.ReactElement {
  const [activeItem, setActiveItem] = React.useState("Dashboard");

  return (
    <>
      {/* The first row holds the logo, and the collapse button in the
          inset layout; it keeps the top bar's height beside it. */}
      <Header className="flex-row items-center justify-between gap-1">
        {/* On the collapsed rail the logo's mark expands the sidebar, at the
            height of the collapse button; the workspace below keeps its menu. */}
        <SidebarMenu className="min-w-0 flex-1">
          <SidebarMenuItem expandTrigger>
            <SidebarBrand render={<a href="#home" />}>
              <NorthlightMark />
              Northlight
            </SidebarBrand>
          </SidebarMenuItem>
        </SidebarMenu>
        {layout === "inset" ? <InsetSidebarToggle /> : null}
      </Header>

      {/* The workspace switcher is a row of its own outside SidebarContent,
          so it stays in place while the navigation scrolls. */}
      <SidebarHeader>
        <SidebarMenu>
          <WorkspaceSwitcher />
        </SidebarMenu>
      </SidebarHeader>

      <SidebarContent>
        <SidebarGroup>
          <SidebarNavigation
            activeItem={activeItem}
            items={mainNavigation}
            onActiveItemChange={setActiveItem}
          />
        </SidebarGroup>

        <SidebarGroup>
          <SidebarGroupLabel>Workspace</SidebarGroupLabel>
          <WorkspaceNavigation
            activeItem={activeItem}
            onActiveItemChange={setActiveItem}
          />
        </SidebarGroup>

        <RecentSpaces
          activeItem={activeItem}
          onActiveItemChange={setActiveItem}
        />
      </SidebarContent>

      <SidebarFooter>
        <UpgradeCard />
        <FooterNavigation />
      </SidebarFooter>

      <SidebarRail resizable />
    </>
  );
}

function InsetSidebarToggle(): React.ReactElement {
  return (
    <SidebarTrigger
      aria-label="Collapse sidebar"
      className="group-data-[collapsible=icon]:hidden"
      size="icon-md"
    />
  );
}

/** A neutral placeholder logo: a four-point star in a rounded square. */
function NorthlightMark(): React.ReactElement {
  return (
    <svg aria-hidden="true" className="size-5" viewBox="0 0 20 20">
      <rect
        className="fill-[var(--sp-rw-bg-inverse)]"
        height="20"
        rx="6"
        width="20"
      />
      <path
        className="fill-[var(--sp-rw-fg-inverse)]"
        d="M10 4.5 11.4 8.6 15.5 10 11.4 11.4 10 15.5 8.6 11.4 4.5 10 8.6 8.6Z"
      />
    </svg>
  );
}

function SidebarNavigation({
  activeItem,
  items,
  onActiveItemChange,
}: {
  activeItem: string;
  items: NavigationItem[];
  onActiveItemChange: (label: string) => void;
}): React.ReactElement {
  return (
    <SidebarMenu className="gap-px">
      {items.map((item) => {
        return (
          <SidebarMenuItem key={item.label}>
            <SidebarMenuButton
              active={activeItem === item.label}
              onClick={() => onActiveItemChange(item.label)}
              render={item.href ? <a href={item.href} /> : undefined}
              tooltip={item.label}
              type={item.href ? undefined : "button"}
            >
              <SidebarIcon className="size-5 shrink-0" iconPath={item.icon} />
              <span>{item.label}</span>
              {item.shortcut ? (
                <span className="ms-auto flex gap-px">
                  {item.shortcut.map((key) => (
                    <Kbd key={key}>{key}</Kbd>
                  ))}
                </span>
              ) : null}
            </SidebarMenuButton>
          </SidebarMenuItem>
        );
      })}
    </SidebarMenu>
  );
}

function WorkspaceNavigation({
  activeItem,
  onActiveItemChange,
}: {
  activeItem: string;
  onActiveItemChange: (label: string) => void;
}): React.ReactElement {
  return (
    <SidebarMenu className="gap-px">
      {workspaceNavigation.map((item, index) => (
        <WorkspaceFolderRow
          active={activeItem === item.label}
          hasActions={index === 0}
          item={item}
          key={item.label}
          onSelect={() => onActiveItemChange(item.label)}
        />
      ))}
      <LibraryNavigation
        activeItem={activeItem}
        onActiveItemChange={onActiveItemChange}
      />
    </SidebarMenu>
  );
}

function LibraryNavigation({
  activeItem,
  onActiveItemChange,
}: {
  activeItem: string;
  onActiveItemChange: (label: string) => void;
}): React.ReactElement {
  const { isMobile, state, setOpen } = useSidebar();

  return (
    <Collapsible
      onOpenChange={(open) => {
        if (open && !isMobile && state === "collapsed") setOpen(true);
      }}
      render={<SidebarMenuItem />}
    >
      <CollapsibleTrigger render={<SidebarMenuButton tooltip="Library" />}>
        <LibraryIcon className="size-5" />
        <span>Library</span>
        <span className="ms-1 flex shrink-0 items-center group-data-[collapsible=icon]:hidden">
          <ChevronDownIcon
            className="m-0! size-4"
            data-slot="collapsible-chevron"
          />
        </span>
      </CollapsibleTrigger>
      <CollapsiblePanel>
        <SidebarMenuSub>
          {[
            { label: "Documents", href: "#documents" },
            { label: "Templates", href: "#templates" },
            { label: "Media", href: "#media" },
          ].map((item) => (
            <SidebarMenuSubItem key={item.label}>
              <SidebarMenuSubButton
                active={activeItem === item.label}
                onClick={() => onActiveItemChange(item.label)}
                render={<a href={item.href} />}
              >
                {item.label}
              </SidebarMenuSubButton>
            </SidebarMenuSubItem>
          ))}
        </SidebarMenuSub>
      </CollapsiblePanel>
    </Collapsible>
  );
}

function WorkspaceFolderRow({
  active,
  hasActions,
  item,
  onSelect,
}: {
  active: boolean;
  hasActions: boolean;
  item: NavigationItem;
  onSelect: () => void;
}): React.ReactElement {
  return (
    <SidebarMenuItem className="group/folder-row">
      <SidebarMenuButton
        active={active}
        onClick={onSelect}
        render={<a href={item.href} />}
        tooltip={item.label}
      >
        <SidebarIcon
          aria-hidden="true"
          className="size-5"
          iconPath={item.icon}
        />
        <span>{item.label}</span>
      </SidebarMenuButton>

      {hasActions ? (
        <DropdownMenu>
          <DropdownMenuTrigger
            render={
              <SidebarMenuAction
                aria-label={`Options for ${item.label}`}
                showOnHover
                type="button"
              />
            }
          >
            <SidebarIcon
              className="size-5 shrink-0 opacity-100"
              iconPath={sidebarIconPaths.ellipsis}
            />
          </DropdownMenuTrigger>
          <DropdownMenuContent align="end" className="w-[180px]" sideOffset={4}>
            <DropdownMenuItem>
              <PlusIcon />
              Create space
            </DropdownMenuItem>
            <DropdownMenuItem>
              <FolderPlusIcon />
              Create folder
            </DropdownMenuItem>
          </DropdownMenuContent>
        </DropdownMenu>
      ) : null}
    </SidebarMenuItem>
  );
}

// The switcher is a row of its own under the logo: the workspace's mark, its
// name over its plan and the up-down chevron. Two lines fill the 48 px row; on
// the collapsed rail it is the 36 px square with a 20 px mark, and the mark
// still opens the workspace menu there; the logo above expands the rail.
function WorkspaceSwitcher(): React.ReactElement {
  const { isMobile, state } = useSidebar();
  const collapsed = state === "collapsed" && !isMobile;
  const [activeName, setActiveName] = React.useState<string>(
    workspaces[0].name,
  );
  const active =
    workspaces.find((workspace) => workspace.name === activeName) ??
    workspaces[0];

  return (
    <SidebarMenuItem>
      <DropdownMenu>
        <DropdownMenuTrigger
          render={
            <SidebarMenuButton
              aria-label={`Open workspace switcher for ${active.name}`}
              size={collapsed ? "lg" : "xl"}
              type="button"
              variant="workspace"
            />
          }
        >
          <Avatar
            aria-hidden="true"
            className={collapsed ? "size-5 rounded-md" : "size-9 rounded-md"}
          >
            <AvatarFallback>
              {active.icon ? (
                <active.icon className="size-3/5" />
              ) : (
                active.initial
              )}
            </AvatarFallback>
          </Avatar>
          <SidebarMenuButtonText>
            <SidebarMenuButtonTitle>{active.name}</SidebarMenuButtonTitle>
            <SidebarMenuButtonDescription>
              {active.plan}
            </SidebarMenuButtonDescription>
          </SidebarMenuButtonText>
          <ChevronsUpDownIcon className="size-4 text-[var(--sp-rw-fg-tertiary)]" />
        </DropdownMenuTrigger>
        <DropdownMenuContent
          align="start"
          className={collapsed ? sidebarMenuPopupWidthClassName : "w-[280px]"}
          side="bottom"
          sideOffset={4}
        >
          <DropdownMenuGroup>
            <DropdownMenuLabel>Workspaces</DropdownMenuLabel>
            <DropdownMenuRadioGroup
              onValueChange={(name) => setActiveName(String(name))}
              value={active.name}
            >
              {workspaces.map((workspace) => (
                <DropdownMenuRadioItem
                  closeOnClick
                  indicatorPosition="end"
                  key={workspace.name}
                  value={workspace.name}
                >
                  <Avatar aria-hidden="true" className="size-6 rounded-md">
                    <AvatarFallback>
                      {workspace.icon ? (
                        <workspace.icon className="size-3/5" />
                      ) : (
                        workspace.initial
                      )}
                    </AvatarFallback>
                  </Avatar>
                  {workspace.name}
                </DropdownMenuRadioItem>
              ))}
            </DropdownMenuRadioGroup>
          </DropdownMenuGroup>
          <DropdownMenuSeparator />
          <DropdownMenuItem>
            <PlusIcon />
            <span>New workspace</span>
          </DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
    </SidebarMenuItem>
  );
}

function RecentSpaces({
  activeItem,
  onActiveItemChange,
}: {
  activeItem: string;
  onActiveItemChange: (label: string) => void;
}): React.ReactElement {
  return (
    <SidebarGroup className="group-data-[collapsible=icon]:hidden">
      <SidebarGroupLabel>Recently used</SidebarGroupLabel>
      <SidebarMenu>
        <SidebarMenuItem className="group/project-row">
          <SidebarMenuButton
            active={activeItem === recentSpace}
            onClick={() => onActiveItemChange(recentSpace)}
            render={<a href="#recent-space" />}
            tooltip={recentSpace}
          >
            <span>{recentSpace}</span>
          </SidebarMenuButton>
          <DropdownMenu>
            <DropdownMenuTrigger
              render={
                <SidebarMenuAction
                  aria-label={`More options for ${recentSpace}`}
                  showOnHover
                  type="button"
                />
              }
            >
              <SidebarIcon
                className="size-5 shrink-0 opacity-100"
                iconPath={sidebarIconPaths.ellipsis}
              />
            </DropdownMenuTrigger>
            <DropdownMenuContent
              align="end"
              className="w-[198px]"
              side="right"
              sideOffset={4}
            >
              <DropdownMenuItem>
                <StarOffIcon />
                Unstar
              </DropdownMenuItem>
              <DropdownMenuItem>
                <FolderInputIcon />
                Move to folder
              </DropdownMenuItem>
              <DropdownMenuItem>
                <CopyIcon />
                Duplicate
              </DropdownMenuItem>
              <DropdownMenuItem>
                <PencilIcon />
                Rename
              </DropdownMenuItem>
              <DropdownMenuItem>
                <SettingsIcon />
                Settings
              </DropdownMenuItem>
              <DropdownMenuSeparator />
              <DropdownMenuItem variant="destructive">
                <Trash2Icon />
                Delete
              </DropdownMenuItem>
            </DropdownMenuContent>
          </DropdownMenu>
        </SidebarMenuItem>
      </SidebarMenu>
    </SidebarGroup>
  );
}

function UpgradeCard(): React.ReactElement {
  return (
    <Card className="group-data-[collapsible=icon]:hidden">
      <CardPanel className="flex items-center justify-between gap-3">
        <span className="flex min-w-0 flex-col">
          <span className="truncate font-medium text-sm leading-5">
            Make room for more
          </span>
          <span className="truncate text-xs leading-4 text-[var(--sp-rw-fg-tertiary)]">
            Explore workspace plans
          </span>
        </span>
        <Button
          aria-label="Explore workspace plans"
          render={<a href="#billing" />}
          shape="pill"
          size="icon-md"
          variant="muted-accent"
        >
          <ArrowUpRightIcon />
        </Button>
      </CardPanel>
    </Card>
  );
}

function FooterNavigation(): React.ReactElement {
  const [theme, setTheme] = React.useState("system");

  // On the collapsed rail the Messages link stacks above the avatar in the
  // rail's 36 px square: the avatar keeps its place at the bottom, and both
  // stand on the rail's axis from the first frame of the collapse.
  return (
    <div className="flex items-center justify-between group-data-[collapsible=icon]:w-(--sp-rw-control-lg) group-data-[collapsible=icon]:flex-col-reverse group-data-[collapsible=icon]:gap-2">
      <DropdownMenu>
        <Tooltip>
          <TooltipTrigger
            delay={0}
            render={
              <DropdownMenuTrigger
                render={
                  <Button
                    aria-label="Profile"
                    profile="avatar"
                    size="icon-md"
                    type="button"
                    variant="ghost"
                  />
                }
              />
            }
          >
            <Avatar>
              <AvatarFallback>{account.avatar}</AvatarFallback>
            </Avatar>
          </TooltipTrigger>
          <TooltipPopup side="right">Profile</TooltipPopup>
        </Tooltip>
        <DropdownMenuContent
          align="start"
          className="w-[220px]"
          side="top"
          sideOffset={4}
        >
          <DropdownMenuGroup>
            <DropdownMenuLabel className="flex items-center gap-2">
              <Avatar aria-hidden="true">
                <AvatarFallback>{account.avatar}</AvatarFallback>
              </Avatar>
              <span className="grid min-w-0">
                <span className="truncate font-medium text-sm text-[var(--sp-rw-fg-primary)]">
                  {account.name}
                </span>
                <span className="truncate font-normal text-xs text-[var(--sp-rw-fg-tertiary)]">
                  {account.email}
                </span>
              </span>
            </DropdownMenuLabel>
          </DropdownMenuGroup>
          <DropdownMenuSeparator />
          <DropdownMenuItem>
            <UserIcon />
            <span>Profile</span>
          </DropdownMenuItem>
          <DropdownMenuItem>
            <SettingsIcon />
            <span>Settings</span>
            <DropdownMenuShortcut>⌘,</DropdownMenuShortcut>
          </DropdownMenuItem>
          <DropdownMenuSub>
            <DropdownMenuSubTrigger>
              <SunMoonIcon />
              <span>Appearance</span>
            </DropdownMenuSubTrigger>
            <DropdownMenuSubContent className="w-[180px]">
              <DropdownMenuRadioGroup onValueChange={setTheme} value={theme}>
                <DropdownMenuRadioItem value="system">
                  System
                </DropdownMenuRadioItem>
                <DropdownMenuRadioItem value="light">
                  Light
                </DropdownMenuRadioItem>
                <DropdownMenuRadioItem value="dark">Dark</DropdownMenuRadioItem>
              </DropdownMenuRadioGroup>
            </DropdownMenuSubContent>
          </DropdownMenuSub>
          <DropdownMenuSeparator />
          <DropdownMenuSub>
            <DropdownMenuSubTrigger>
              <CircleHelpIcon />
              <span>Support</span>
            </DropdownMenuSubTrigger>
            <DropdownMenuSubContent className="w-[180px]">
              <DropdownMenuItem>
                <LifeBuoyIcon />
                Help center
              </DropdownMenuItem>
              <DropdownMenuItem>
                <HeadsetIcon />
                Contact support
              </DropdownMenuItem>
            </DropdownMenuSubContent>
          </DropdownMenuSub>
          <DropdownMenuSub>
            <DropdownMenuSubTrigger>
              <BookOpenIcon />
              <span>Documentation</span>
            </DropdownMenuSubTrigger>
            <DropdownMenuSubContent className="w-[180px]">
              <DropdownMenuItem>
                <BookMarkedIcon />
                Guides
              </DropdownMenuItem>
              <DropdownMenuItem>
                <LayersIcon />
                Changelog
              </DropdownMenuItem>
            </DropdownMenuSubContent>
          </DropdownMenuSub>
          <DropdownMenuItem>
            <UsersIcon />
            <span>Community</span>
            <ArrowUpRightIcon />
          </DropdownMenuItem>
          <DropdownMenuItem>
            <HouseIcon />
            <span>Homepage</span>
          </DropdownMenuItem>
          <DropdownMenuSeparator />
          <DropdownMenuItem>
            <LogOutIcon />
            <span>Sign out</span>
          </DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>

      <Tooltip>
        <TooltipTrigger
          delay={0}
          render={
            <Button
              aria-label="Messages"
              profile="inline"
              profileTone="muted"
              render={<a href="#messages" />}
              size="xl"
              variant="text"
            />
          }
        >
          <SidebarIcon
            className="shrink-0 opacity-100"
            iconPath={sidebarIconPaths.inbox}
          />
        </TooltipTrigger>
        <TooltipPopup side="right">Messages</TooltipPopup>
      </Tooltip>
    </div>
  );
}
```

## Structure the page

Keep navigation in DashboardShellSidebar, the page heading and trigger in DashboardShellHeader, and scrollable content in DashboardShellMain. DashboardShellPrimary can group header and content in a flexible region. Avoid nesting a second main landmark inside DashboardShellInset, which already renders main.

Use Breadcrumb for the current location, Tabs for related views, and ordinary application headings and sections for content. Keep the selected route in the router or application state and set each menu button’s active prop accordingly.

## Desktop and mobile

Desktop supports offcanvas or icon collapse; DashboardShell defaults to icon. Mobile uses a Sheet and has separate open state. Keep SidebarTrigger available at narrow widths. Icon-only navigation needs actual icons and accessible labels/tooltips.

The sidebar registers Ctrl/Cmd+B and writes desktop expansion to the `sidebar_state` cookie. Read the [Sidebar usage notes](/docs/components/sidebar/#usage-notes) before choosing persistence behavior. The provider does not automatically restore the cookie on first render.

## Bounded previews and production layout

The documentation example includes a bounded application wrapper so the sidebar stays inside the visible sample. That wrapper is part of the copied TSX, not an invisible docs style. Remove it, and the `h-full` and `min-h-0` classes it gives the shell and the sidebar, when the shell should fill the application viewport. The header then stays in view while DashboardShellMain scrolls, as the [sticky header example](/docs/components/dashboard-shell/#sticky-header) shows. Set `stickyHeader={false}` on DashboardShell for a page that scrolls as a whole.

## Complete application

[Application shell](/docs/examples/application-shell/) provides the complete file, installation command, and local navigation. Integrate your router through menu button render composition; connect data, session state, and authorization in the application.
