# Sidebar

Build application navigation with desktop collapse and a mobile sheet.

Inset workspace sidebar

[Complete TSX file](/examples/sidebar/inset.tsx)

```tsx
"use client";

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 {
  Sidebar,
  SidebarBrand,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarGroupLabel,
  SidebarHeader,
  SidebarInset,
  SidebarMenu,
  SidebarMenuAction,
  SidebarMenuButton,
  SidebarMenuButtonDescription,
  SidebarMenuButtonText,
  SidebarMenuButtonTitle,
  SidebarMenuItem,
  SidebarMenuSub,
  SidebarMenuSubButton,
  SidebarMenuSubItem,
  SidebarProvider,
  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: SidebarPatternLayout;
}): React.ReactElement {
  const push = layout === "push";

  return (
    <div
      className="relative h-[640px] w-full overflow-hidden bg-[var(--sp-rw-bg-base)] [transform:translateZ(0)]"
      data-pattern="sidebar-workspace-example"
      data-sidebar-layout={layout}
    >
      <SidebarProvider className="h-full min-h-0!" layout={layout} defaultOpen>
        <Sidebar className="h-full!" collapsible="icon">
          <SidebarChrome layout={layout} />
        </Sidebar>

        <SidebarInset className="overflow-clip">
          <header className="flex h-12 shrink-0 items-center gap-2 px-4">
            <SidebarTrigger
              aria-label="Toggle sidebar"
              className={push ? undefined : "md:hidden"}
              size="icon-md"
            />
            <span className="text-sm text-[var(--sp-rw-fg-tertiary)]">
              Workspace
            </span>
            <span
              aria-hidden="true"
              className="text-[var(--sp-rw-fg-quaternary)]"
            >
              /
            </span>
            <span className="font-medium text-sm">Dashboard</span>
          </header>

          <div className="flex flex-1 flex-col gap-5 overflow-auto p-6">
            <div>
              <h2 className="font-medium text-xl">Welcome back</h2>
              <p className="mt-1 text-sm text-[var(--sp-rw-fg-tertiary)]">
                Continue building your Acme workspace.
              </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>
            <Card className="h-40" />
          </div>
        </SidebarInset>
      </SidebarProvider>
    </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>
  );
}
```

## Installation

Complete [one-time setup](/docs/installation/) first.

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/sidebar
```

With npm: `npx shadcn@4.18.0 add @siteplane/sidebar`. [Registry JSON](/registry/sidebar.json) lists source files and dependencies.

For this page’s examples, also install:

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/avatar @siteplane/button @siteplane/card @siteplane/collapsible @siteplane/dropdown-menu @siteplane/kbd @siteplane/tooltip
```

Or install the complete inset example with its dependencies; use `@siteplane/sidebar-demo` for the push layout:

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/sidebar-inset-example
```

[Open the full-width interactive example](/preview/sidebar/inset/). Set `layout="inset"` on SidebarProvider for a framed content surface. The first row holds the logo and the collapse button, and the workspace switcher follows in a row of its own; when the sidebar is collapsed, hover or focus the logo's mark to reveal the expand control. Below 768 px, the header toggle opens the navigation as a sheet.

## Usage

```tsx
import {
  SidebarProvider,
  Sidebar,
  SidebarContent,
  SidebarInset,
  SidebarTrigger,
} from "@/siteplane/components/sidebar";

<SidebarProvider>
  <Sidebar>
    <SidebarContent>Navigation</SidebarContent>
  </Sidebar>
  <SidebarInset>
    <SidebarTrigger />
    Content
  </SidebarInset>
</SidebarProvider>;
```

## Composition

```text
SidebarProvider
  Sidebar
    SidebarHeader → SidebarBrand + SidebarTrigger
    SidebarHeader → SidebarMenu → SidebarMenuItem (workspace switcher)
    SidebarContent → SidebarGroup → SidebarGroupLabel + SidebarGroupContent
      SidebarMenu → SidebarMenuItem → SidebarMenuButton + SidebarMenuAction + SidebarMenuBadge
        SidebarMenuButton → SidebarMenuButtonText → SidebarMenuButtonTitle + SidebarMenuButtonDescription
        SidebarMenuSub → SidebarMenuSubItem → SidebarMenuSubButton
    SidebarFooter
    SidebarRail
  SidebarInset → SidebarTrigger + application content
```

The complete examples combine these parts:

| Feature                         | Public composition                                     |
| ------------------------------- | ------------------------------------------------------ |
| Application logo                | `SidebarBrand` in a `SidebarHeader`                    |
| Named navigation groups         | `SidebarGroup` + `SidebarGroupLabel` + `SidebarMenu`   |
| Expandable nested links         | `Collapsible` + `SidebarMenuButton` + `SidebarMenuSub` |
| Workspace and account dropdowns | `DropdownMenu` with a sidebar button as its trigger    |
| Name over a detail line         | `SidebarMenuButtonText` in a `size="xl"` menu button   |
| Per-item actions                | `SidebarMenuAction` + `DropdownMenu`                   |
| Desktop collapse and resize     | `SidebarTrigger` + `SidebarRail resizable`             |
| Mobile navigation               | `SidebarProvider` + `Sidebar` (built-in Sheet)         |

## Push layout

Set `layout="push"` on SidebarProvider for one unframed content surface from the sidebar to the edge. The header button toggles the sidebar; expanding it pushes the header and content aside instead of covering them. SidebarInset moves its content at the sidebar's pace, so its children keep their own padding and need no offset or motion code. Put padding on those children rather than on SidebarInset: a padding utility on SidebarInset replaces the offset.

Push workspace sidebar

[Complete TSX file](/examples/sidebar/default.tsx)

```tsx
"use client";

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 {
  Sidebar,
  SidebarBrand,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarGroupLabel,
  SidebarHeader,
  SidebarInset,
  SidebarMenu,
  SidebarMenuAction,
  SidebarMenuButton,
  SidebarMenuButtonDescription,
  SidebarMenuButtonText,
  SidebarMenuButtonTitle,
  SidebarMenuItem,
  SidebarMenuSub,
  SidebarMenuSubButton,
  SidebarMenuSubItem,
  SidebarProvider,
  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: SidebarPatternLayout;
}): React.ReactElement {
  const push = layout === "push";

  return (
    <div
      className="relative h-[640px] w-full overflow-hidden bg-[var(--sp-rw-bg-base)] [transform:translateZ(0)]"
      data-pattern="sidebar-workspace-example"
      data-sidebar-layout={layout}
    >
      <SidebarProvider className="h-full min-h-0!" layout={layout} defaultOpen>
        <Sidebar className="h-full!" collapsible="icon">
          <SidebarChrome layout={layout} />
        </Sidebar>

        <SidebarInset className="overflow-clip">
          <header className="flex h-12 shrink-0 items-center gap-2 px-4">
            <SidebarTrigger
              aria-label="Toggle sidebar"
              className={push ? undefined : "md:hidden"}
              size="icon-md"
            />
            <span className="text-sm text-[var(--sp-rw-fg-tertiary)]">
              Workspace
            </span>
            <span
              aria-hidden="true"
              className="text-[var(--sp-rw-fg-quaternary)]"
            >
              /
            </span>
            <span className="font-medium text-sm">Dashboard</span>
          </header>

          <div className="flex flex-1 flex-col gap-5 overflow-auto p-6">
            <div>
              <h2 className="font-medium text-xl">Welcome back</h2>
              <p className="mt-1 text-sm text-[var(--sp-rw-fg-tertiary)]">
                Continue building your Acme workspace.
              </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>
            <Card className="h-40" />
          </div>
        </SidebarInset>
      </SidebarProvider>
    </div>
  );
}

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

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

[Open the full-width push example](/preview/sidebar/default/).

Layout sidebars retain their leading 8 px gutter when switching to the mobile
Sheet, matching the trailing gutter in their header, content and footer.
The account trigger uses Button's `profile="avatar"` for its circular portrait
and shared keyboard focus outline.

## Header layouts

`SidebarBrand` holds the application's logo: its mark first, then its name or wordmark. It starts where the menu rows start their icons, and a header band around it keeps the height of a 48 px top bar beside it, also with a 32 px collapse button in the same row. Render it as a link with `render`, such as `render={<a href="/" />}`; it takes the kit's focus outline and no hover fill. On the collapsed icon rail it shows only its mark, in the rail's 36 px square like the row icons: the mark keeps its place while its name fades out with the rail's width, as the row labels do, and fades back in when the sidebar expands. A plain-text name gets a span of its own, so a long one ends with an ellipsis and keeps its full text in a `title` attribute, and on the rail it stays the link's accessible name.

The top of the sidebar takes one of three layouts. The examples use the inset layout, where the first row holds the collapse button; there, a row with the logo moves down with the framed surface beside it and stays level with the top bar on it.

### Workspace on top

The workspace switcher and the collapse button share the first row. On the collapsed rail the switcher shows its company mark, which expands the sidebar through `expandTrigger`.

Workspace on top

[Complete TSX file](/examples/sidebar/header-workspace-on-top.tsx)

```tsx
"use client";

import {
  ChevronDownIcon,
  CircleDotIcon,
  FolderIcon,
  HouseIcon,
  InboxIcon,
  type LucideIcon,
  OrbitIcon,
  PlusIcon,
  SearchIcon,
} from "lucide-react";
import * as React from "react";

import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuRadioGroup,
  DropdownMenuRadioItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/siteplane/components/dropdown-menu";
import {
  Sidebar,
  SidebarContent,
  SidebarGroup,
  SidebarHeader,
  SidebarInset,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarProvider,
  SidebarRail,
  SidebarTrigger,
  sidebarMenuPopupWidthClassName,
  useSidebar,
} from "@/siteplane/components/sidebar";

const navigation = [
  { icon: HouseIcon, label: "Home" },
  { icon: SearchIcon, label: "Search" },
  { icon: InboxIcon, label: "Inbox" },
  { icon: FolderIcon, label: "Projects" },
] as const;

const workspaces: { icon: LucideIcon; name: string }[] = [
  { icon: CircleDotIcon, name: "Acme" },
  { icon: OrbitIcon, name: "Acme Labs" },
];

export default function Example() {
  const [active, setActive] = React.useState<string>("Home");

  return (
    <div className="relative h-[32rem] w-full overflow-hidden bg-[var(--sp-rw-bg-base)] [transform:translateZ(0)]">
      <SidebarProvider className="h-full min-h-0" layout="inset">
        <Sidebar className="h-full" collapsible="icon">
          {/* The workspace switcher and the collapse button share the first
              row. */}
          <SidebarHeader className="flex-row items-center gap-1">
            <SidebarMenu className="min-w-0 flex-1">
              <WorkspaceSwitcher />
            </SidebarMenu>
            <SidebarTrigger
              aria-label="Collapse sidebar"
              className="group-data-[collapsible=icon]:hidden"
              size="icon-md"
            />
          </SidebarHeader>
          <SidebarContent>
            <SidebarGroup>
              <SidebarMenu>
                {navigation.map(({ icon: Icon, label }) => (
                  <SidebarMenuItem key={label}>
                    <SidebarMenuButton
                      active={active === label}
                      onClick={() => setActive(label)}
                      tooltip={label}
                    >
                      <Icon />
                      <span>{label}</span>
                    </SidebarMenuButton>
                  </SidebarMenuItem>
                ))}
              </SidebarMenu>
            </SidebarGroup>
          </SidebarContent>
          <SidebarRail />
        </Sidebar>
        <SidebarInset>
          <header className="flex h-12 shrink-0 items-center gap-2 px-4">
            <SidebarTrigger
              aria-label="Toggle sidebar"
              className="md:hidden"
              size="icon-md"
            />
            <h1 className="font-medium text-sm">{active}</h1>
          </header>
          <div className="p-4 text-sm text-[var(--sp-rw-fg-tertiary)]">
            Your workspace content.
          </div>
        </SidebarInset>
      </SidebarProvider>
    </div>
  );
}

// The company mark and the workspace name in one row. On the collapsed rail
// the mark also expands the sidebar: hover or focus it to reveal the control.
function WorkspaceSwitcher(): React.ReactElement {
  const { isMobile, state } = useSidebar();
  const [activeName, setActiveName] = React.useState(workspaces[0]!.name);
  const active =
    workspaces.find((workspace) => workspace.name === activeName) ??
    workspaces[0]!;

  return (
    <SidebarMenuItem expandTrigger>
      <DropdownMenu>
        <DropdownMenuTrigger
          render={
            <SidebarMenuButton
              aria-label={`Open workspace switcher for ${active.name}`}
              type="button"
              variant="workspace"
            />
          }
        >
          <CompanyMark icon={active.icon} />
          <span>{active.name}</span>
          <ChevronDownIcon className="ms-auto size-4 text-[var(--sp-rw-fg-tertiary)]" />
        </DropdownMenuTrigger>
        <DropdownMenuContent
          align="start"
          className={
            state === "collapsed" && !isMobile
              ? sidebarMenuPopupWidthClassName
              : "w-64"
          }
          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}
                >
                  <CompanyMark className="size-6" icon={workspace.icon} />
                  {workspace.name}
                </DropdownMenuRadioItem>
              ))}
            </DropdownMenuRadioGroup>
          </DropdownMenuGroup>
          <DropdownMenuSeparator />
          <DropdownMenuItem>
            <PlusIcon />
            <span>New workspace</span>
          </DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
    </SidebarMenuItem>
  );
}

function CompanyMark({
  className = "size-5",
  icon: Icon,
}: {
  className?: string;
  icon: LucideIcon;
}): React.ReactElement {
  return (
    <span
      aria-hidden="true"
      className={`flex shrink-0 items-center justify-center rounded-full bg-[var(--sp-rw-bg-inverse)] text-[var(--sp-rw-fg-inverse)] ${className}`}
    >
      <Icon className="size-3/5" />
    </span>
  );
}
```

### Logo above the workspace

The first row holds the logo and the collapse button; in the push layout the top bar's button toggles the sidebar and the first row holds the logo alone. The workspace switcher follows in a SidebarHeader of its own, outside SidebarContent, so it stays in place while the navigation scrolls. It shows the workspace's mark and its name over its plan, in a [two-line row](#two-line-rows). On the collapsed rail the logo shows its mark, and the collapse button hides: the logo sits in a SidebarMenuItem with `expandTrigger`, so hovering or focusing its mark reveals the expand control at the height the collapse button had. The workspace mark below keeps opening the workspace menu. The examples at the top of this page use this layout.

Logo above the workspace

[Complete TSX file](/examples/sidebar/header-logo-above-workspace.tsx)

```tsx
"use client";

import {
  ChevronsUpDownIcon,
  CircleDotIcon,
  FolderIcon,
  HouseIcon,
  InboxIcon,
  PlusIcon,
  SearchIcon,
} from "lucide-react";
import * as React from "react";

import { Avatar, AvatarFallback } from "@/siteplane/components/avatar";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuRadioGroup,
  DropdownMenuRadioItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/siteplane/components/dropdown-menu";
import {
  Sidebar,
  SidebarBrand,
  SidebarContent,
  SidebarGroup,
  SidebarHeader,
  SidebarInset,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuButtonDescription,
  SidebarMenuButtonText,
  SidebarMenuButtonTitle,
  SidebarMenuItem,
  SidebarProvider,
  SidebarRail,
  SidebarTrigger,
  sidebarMenuPopupWidthClassName,
  useSidebar,
} from "@/siteplane/components/sidebar";

const navigation = [
  { icon: HouseIcon, label: "Home" },
  { icon: SearchIcon, label: "Search" },
  { icon: InboxIcon, label: "Inbox" },
  { icon: FolderIcon, label: "Projects" },
] as const;

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;

export default function Example() {
  const [active, setActive] = React.useState<string>("Home");

  return (
    <div className="relative h-[32rem] w-full overflow-hidden bg-[var(--sp-rw-bg-base)] [transform:translateZ(0)]">
      <SidebarProvider className="h-full min-h-0" layout="inset">
        <Sidebar className="h-full" collapsible="icon">
          {/* The first row: the logo and the collapse button, as tall as the
              top bar beside it. */}
          <SidebarHeader 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>
            <SidebarTrigger
              aria-label="Collapse sidebar"
              className="group-data-[collapsible=icon]:hidden"
              size="icon-md"
            />
          </SidebarHeader>
          {/* 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>
              <SidebarMenu>
                {navigation.map(({ icon: Icon, label }) => (
                  <SidebarMenuItem key={label}>
                    <SidebarMenuButton
                      active={active === label}
                      onClick={() => setActive(label)}
                      tooltip={label}
                    >
                      <Icon />
                      <span>{label}</span>
                    </SidebarMenuButton>
                  </SidebarMenuItem>
                ))}
              </SidebarMenu>
            </SidebarGroup>
          </SidebarContent>
          <SidebarRail />
        </Sidebar>
        <SidebarInset>
          <header className="flex h-12 shrink-0 items-center gap-2 px-4">
            <SidebarTrigger
              aria-label="Toggle sidebar"
              className="md:hidden"
              size="icon-md"
            />
            <h1 className="font-medium text-sm">{active}</h1>
          </header>
          <div className="p-4 text-sm text-[var(--sp-rw-fg-tertiary)]">
            Your workspace content.
          </div>
        </SidebarInset>
      </SidebarProvider>
    </div>
  );
}

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

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

### Logo only

The first row holds the logo and the collapse button, without a workspace switcher. Put SidebarBrand in a SidebarMenuItem with `expandTrigger`, as in the layout above: on the collapsed rail, hovering or focusing the logo's mark reveals the expand control in its place, so the rail opens again from its first row.

Logo only

[Complete TSX file](/examples/sidebar/header-logo-only.tsx)

```tsx
"use client";

import { FolderIcon, HouseIcon, InboxIcon, SearchIcon } from "lucide-react";
import * as React from "react";

import {
  Sidebar,
  SidebarBrand,
  SidebarContent,
  SidebarGroup,
  SidebarHeader,
  SidebarInset,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarProvider,
  SidebarRail,
  SidebarTrigger,
} from "@/siteplane/components/sidebar";

const navigation = [
  { icon: HouseIcon, label: "Home" },
  { icon: SearchIcon, label: "Search" },
  { icon: InboxIcon, label: "Inbox" },
  { icon: FolderIcon, label: "Projects" },
] as const;

export default function Example() {
  const [active, setActive] = React.useState<string>("Home");

  return (
    <div className="relative h-[32rem] w-full overflow-hidden bg-[var(--sp-rw-bg-base)] [transform:translateZ(0)]">
      <SidebarProvider className="h-full min-h-0" layout="inset">
        <Sidebar className="h-full" collapsible="icon">
          {/* The logo and the collapse button, as tall as the top bar beside
              them. With expandTrigger, the logo's mark also expands the
              collapsed rail: hover or focus it to reveal the control. */}
          <SidebarHeader className="flex-row items-center gap-1">
            <SidebarMenu className="min-w-0 flex-1">
              <SidebarMenuItem expandTrigger>
                <SidebarBrand render={<a href="#home" />}>
                  <NorthlightMark />
                  Northlight
                </SidebarBrand>
              </SidebarMenuItem>
            </SidebarMenu>
            <SidebarTrigger
              aria-label="Collapse sidebar"
              className="group-data-[collapsible=icon]:hidden"
              size="icon-md"
            />
          </SidebarHeader>
          <SidebarContent>
            <SidebarGroup>
              <SidebarMenu>
                {navigation.map(({ icon: Icon, label }) => (
                  <SidebarMenuItem key={label}>
                    <SidebarMenuButton
                      active={active === label}
                      onClick={() => setActive(label)}
                      tooltip={label}
                    >
                      <Icon />
                      <span>{label}</span>
                    </SidebarMenuButton>
                  </SidebarMenuItem>
                ))}
              </SidebarMenu>
            </SidebarGroup>
          </SidebarContent>
          <SidebarRail />
        </Sidebar>
        <SidebarInset>
          <header className="flex h-12 shrink-0 items-center gap-2 px-4">
            <SidebarTrigger
              aria-label="Toggle sidebar"
              className="md:hidden"
              size="icon-md"
            />
            <h1 className="font-medium text-sm">{active}</h1>
          </header>
          <div className="p-4 text-sm text-[var(--sp-rw-fg-tertiary)]">
            Your workspace content.
          </div>
        </SidebarInset>
      </SidebarProvider>
    </div>
  );
}

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

## Sidebar spacing

With a `layout`, the Sidebar reserves the leading 8 px gutter on its container,
so SidebarHeader, SidebarGroup and SidebarFooter add only their trailing inset
and keep their block padding. Do not override their padding to remove the
leading side. SidebarHeader and SidebarFooter are bands with 8 px above and
below, and SidebarFooter spaces its groups 16 px apart, like the groups of the
content. SidebarContent scrolls between the two bands and keeps its trailing
inset inside the scroll extent, so the last row never ends flush with, or
under, the footer card.

A sidebar sits flat on the page background in both layouts, with no frame,
border or shadow of its own; the main surface carries the edge. From the
mobile breakpoint up, SidebarInset is the primary surface beside it, with a
`layout` or without one, so the sidebar and the content never share one grey,
and the cards on it are white with a visible hairline.

The examples show the logo of a fictional application, Northlight, at the
top, the switcher for its Acme workspace under it, and the signed-in person
(initials avatar, name and email in its menu) as the account at the bottom, so
none of them repeats another.

## Menu row spacing

SidebarMenuButton and SidebarMenuSubButton own their inline padding: 8 px on
both sides, including text-only rows. Leading icons and trailing controls do
not supply that spacing. Do not add compensating padding to label, shortcut or
chevron wrappers. Place a 20 px icon or avatar directly before the label;
do not wrap it in an additional spacing container. On the collapsed icon rail,
main rows keep that same inset, so the icon stays where it was, and become
squares as wide as the row is high. The rail is 52 px wide, which is the 8 px
inset, the 36 px square and the 8 px inset again: the toggle, the workspace
mark, every icon and the active pill share the rail's center axis. Header and
footer items keep the inset they have in the expanded sidebar, as the rows do,
so the width animation never moves them: a 36 px item, such as the logo's
square or a row, stands on the axis, and a group of smaller controls takes the
square's width on the rail
(`group-data-[collapsible=icon]:w-(--sp-rw-control-lg)`) and centers them in
it. The examples stack their Messages control above the account there, so the
account stays at the bottom of the rail, where it was. Labels and
trailing content fade out and remain available to assistive technology. A
group label keeps its row, so the groups stay apart and the icons below it do
not move. Explicitly sized icons use the same margin reset as default-sized
icons.

A label that is too long for the sidebar ends with an ellipsis. In a menu row
this holds for every text span after the row's icon or avatar, also when a
chevron follows it, so the chevron stays in the row; kit parts with a slot of
their own, such as an Avatar or a Badge, keep their size. A plain-text
SidebarGroupLabel ends with an ellipsis as well and keeps its full text in a
`title` attribute.

SidebarContent owns scrolling. With a `layout` it borrows the sidebar's leading
gutter so the shared focus outline is not clipped, without changing row
alignment; without a layout its scroll viewport stays inside the sidebar and
the groups' own padding keeps the outline clear. Keep navigation groups inside
that scroll area; another overflow container can clip the outline again.

## Icon collapse and resize rail

Place an icon before each menu label. On the collapsed rail only the icon stays visible, and `tooltip` names the item on hover or keyboard focus.

`SidebarRail` is an 8 px strip along the whole outer edge of the sidebar, clear of its scrollbar. A click on it toggles the sidebar. With `resizable`, dragging it resizes the expanded sidebar between `--sidebar-width-min` and `--sidebar-width-max` (12rem and 24rem by default). The rail stays out of the Tab order, because SidebarTrigger and Ctrl/Cmd+B toggle the sidebar from the keyboard.

Icon collapse and resize rail

[Complete TSX file](/examples/sidebar/icon.tsx)

```tsx
"use client";

import {
  Sidebar,
  SidebarBrand,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarGroupLabel,
  SidebarHeader,
  SidebarInset,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarProvider,
  SidebarRail,
  SidebarTrigger,
} from "@/siteplane/components/sidebar";
import {
  CircleDotIcon,
  FolderIcon,
  HomeIcon,
  SettingsIcon,
} from "lucide-react";
import { useState } from "react";

export default function Example() {
  const [active, setActive] = useState("Overview");
  return (
    <div className="relative h-[32rem] w-full overflow-hidden [transform:translateZ(0)]">
      <SidebarProvider className="h-full min-h-0">
        <Sidebar className="h-full" collapsible="icon">
          <SidebarHeader>
            {/* The mark stays on the collapsed rail; the name leaves it. */}
            <SidebarBrand>
              <span
                aria-hidden="true"
                className="flex size-5 shrink-0 items-center justify-center rounded-full bg-[var(--sp-rw-bg-inverse)] text-[var(--sp-rw-fg-inverse)]"
              >
                <CircleDotIcon className="size-3/5 opacity-100" />
              </span>
              Acme
            </SidebarBrand>
          </SidebarHeader>
          <SidebarContent>
            <SidebarGroup>
              <SidebarGroupLabel>Workspace</SidebarGroupLabel>
              <SidebarMenu>
                {[
                  { label: "Overview", icon: HomeIcon },
                  { label: "Projects", icon: FolderIcon },
                  { label: "Settings", icon: SettingsIcon },
                ].map(({ label, icon: Icon }) => (
                  <SidebarMenuItem key={label}>
                    <SidebarMenuButton
                      tooltip={label}
                      active={active === label}
                      onClick={() => setActive(label)}
                    >
                      <Icon />
                      <span>{label}</span>
                    </SidebarMenuButton>
                  </SidebarMenuItem>
                ))}
              </SidebarMenu>
            </SidebarGroup>
          </SidebarContent>
          <SidebarFooter>
            <span className="truncate px-2 text-xs text-muted-foreground transition-opacity duration-(--motion-duration-base) ease-(--motion-ease-linear) group-data-[collapsible=icon]:opacity-0">
              Acme workspace
            </span>
          </SidebarFooter>
          <SidebarRail resizable />
        </Sidebar>
        <SidebarInset>
          <header className="flex items-center gap-2 border-b p-3">
            <SidebarTrigger />
            <h1 className="font-semibold">{active}</h1>
          </header>
          <div className="p-4 text-sm">Your workspace content.</div>
        </SidebarInset>
      </SidebarProvider>
    </div>
  );
}
```

## Search, badges and nested links

Add `SidebarInput` for search, `SidebarMenuBadge` for counts, `SidebarMenuSub` for nested pages and `SidebarMenuSkeleton` while items load. A menu button keeps room at its end for a badge or a `SidebarMenuAction`, so a long label truncates before it. `SidebarMenuAction` renders Button's [bare icon action](/docs/components/button/#bare-icon-actions), an icon in a square target of the control size with no hover or pressed background. With `showOnHover` it shows only while its row is hovered or focused, or while its menu is open.

Sidebar search, badges and nested links

[Complete TSX file](/examples/sidebar/content.tsx)

```tsx
"use client";

import {
  Sidebar,
  SidebarContent,
  SidebarGroup,
  SidebarGroupLabel,
  SidebarHeader,
  SidebarInput,
  SidebarInset,
  SidebarMenu,
  SidebarMenuBadge,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarMenuSkeleton,
  SidebarMenuSub,
  SidebarMenuSubButton,
  SidebarMenuSubItem,
  SidebarProvider,
  SidebarSeparator,
} from "@/siteplane/components/sidebar";
import { FolderIcon } from "lucide-react";

export default function Example() {
  return (
    <div className="relative h-[32rem] w-full overflow-hidden">
      <SidebarProvider className="h-full min-h-0">
        <Sidebar collapsible="none">
          <SidebarHeader>
            <SidebarInput
              aria-label="Search projects"
              placeholder="Search projects…"
            />
          </SidebarHeader>
          <SidebarSeparator />
          <SidebarContent>
            <SidebarGroup>
              <SidebarGroupLabel>Projects</SidebarGroupLabel>
              <SidebarMenu>
                <SidebarMenuItem>
                  <SidebarMenuButton active render={<a href="#website" />}>
                    <FolderIcon />
                    <span>Website</span>
                  </SidebarMenuButton>
                  <SidebarMenuBadge>3</SidebarMenuBadge>
                  <SidebarMenuSub>
                    <SidebarMenuSubItem>
                      <SidebarMenuSubButton render={<a href="#design" />}>
                        Design
                      </SidebarMenuSubButton>
                    </SidebarMenuSubItem>
                    <SidebarMenuSubItem>
                      <SidebarMenuSubButton render={<a href="#development" />}>
                        Development
                      </SidebarMenuSubButton>
                    </SidebarMenuSubItem>
                  </SidebarMenuSub>
                </SidebarMenuItem>
                <SidebarMenuItem>
                  <SidebarMenuSkeleton showIcon width="65%" />
                </SidebarMenuItem>
              </SidebarMenu>
            </SidebarGroup>
          </SidebarContent>
        </Sidebar>
        <SidebarInset>
          <header className="flex items-center gap-2 border-b p-3">
            <h1 className="font-semibold">Website</h1>
          </header>
          <div className="p-4 text-sm">Your project content.</div>
        </SidebarInset>
      </SidebarProvider>
    </div>
  );
}
```

`SidebarSeparator` is the kit's divider inside the sidebar's 8 px inset. It spans the band it sits in, also where the icon rail centers the band's items, and never runs past the sidebar's edge.

## Two-line rows

A row can show a name over a quieter detail line, such as a workspace and its plan or an account and its email. Put `SidebarMenuButtonText` after the row's avatar or icon, with `SidebarMenuButtonTitle` and `SidebarMenuButtonDescription` inside it. The title keeps the row's text. The description is one kit text step smaller (12 px on an 18 px line, or 14 px on a 21 px line in an xl row) in the tertiary text color. Both stay on one line, end with an ellipsis when they are too long, and keep their full text in a `title` attribute when they are plain text. The text takes the row's free width, so a trailing chevron stays at the row's end. Two lines fill the 48 px row, so give the button `size="xl"`. The collapsed icon rail holds 36 px squares with a 20 px icon: in a sidebar that collapses to it, give the row `size="lg"` and its avatar the 20 px of a row icon (`size-5`) while `useSidebar().state` is `"collapsed"`, so the avatar stays on the rail's center axis. The row follows the switch at the sidebar's pace: its height, padding and text and the avatar's size change with the rail's width, so the rows below glide up instead of jumping. Reduced motion makes the change instant.

Workspace and account rows with a detail line

[Complete TSX file](/examples/sidebar/two-line.tsx)

```tsx
"use client";

import {
  ChevronsUpDownIcon,
  FolderIcon,
  HouseIcon,
  LogOutIcon,
  PlusIcon,
  SettingsIcon,
  UserIcon,
} from "lucide-react";
import { useState } from "react";

import { Avatar, AvatarFallback } from "@/siteplane/components/avatar";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuRadioGroup,
  DropdownMenuRadioItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/siteplane/components/dropdown-menu";
import {
  Sidebar,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarHeader,
  SidebarInset,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuButtonDescription,
  SidebarMenuButtonText,
  SidebarMenuButtonTitle,
  SidebarMenuItem,
  SidebarProvider,
  SidebarSeparator,
} from "@/siteplane/components/sidebar";

const workspaces = [
  { name: "Northlight Studio", plan: "Pro plan", initial: "N" },
  { name: "Copperleaf Spa & Wellness", plan: "Free plan", initial: "C" },
] as const;

export default function Example() {
  const [workspace, setWorkspace] = useState<string>(workspaces[0].name);
  const current =
    workspaces.find((item) => item.name === workspace) ?? workspaces[0];

  return (
    <div className="relative h-[32rem] w-full overflow-hidden">
      <SidebarProvider className="h-full min-h-0">
        <Sidebar collapsible="none">
          <SidebarHeader>
            <SidebarMenu>
              <SidebarMenuItem>
                <DropdownMenu>
                  <DropdownMenuTrigger
                    render={
                      <SidebarMenuButton
                        aria-label={`Switch workspace, current: ${current.name}`}
                        size="xl"
                        type="button"
                        variant="workspace"
                      />
                    }
                  >
                    <Avatar aria-hidden="true" className="rounded-md">
                      <AvatarFallback>{current.initial}</AvatarFallback>
                    </Avatar>
                    <SidebarMenuButtonText>
                      <SidebarMenuButtonTitle>
                        {current.name}
                      </SidebarMenuButtonTitle>
                      <SidebarMenuButtonDescription>
                        {current.plan}
                      </SidebarMenuButtonDescription>
                    </SidebarMenuButtonText>
                    <ChevronsUpDownIcon />
                  </DropdownMenuTrigger>
                  <DropdownMenuContent align="start" className="w-64">
                    <DropdownMenuGroup>
                      <DropdownMenuLabel>Workspaces</DropdownMenuLabel>
                      <DropdownMenuRadioGroup
                        onValueChange={(value) => setWorkspace(String(value))}
                        value={workspace}
                      >
                        {workspaces.map((item) => (
                          <DropdownMenuRadioItem
                            closeOnClick
                            indicatorPosition="end"
                            key={item.name}
                            value={item.name}
                          >
                            <Avatar
                              aria-hidden="true"
                              className="size-5 rounded-md"
                            >
                              <AvatarFallback>{item.initial}</AvatarFallback>
                            </Avatar>
                            {item.name}
                          </DropdownMenuRadioItem>
                        ))}
                      </DropdownMenuRadioGroup>
                    </DropdownMenuGroup>
                    <DropdownMenuSeparator />
                    <DropdownMenuItem>
                      <PlusIcon />
                      New workspace
                    </DropdownMenuItem>
                  </DropdownMenuContent>
                </DropdownMenu>
              </SidebarMenuItem>
            </SidebarMenu>
          </SidebarHeader>
          <SidebarContent>
            <SidebarGroup>
              <SidebarMenu>
                <SidebarMenuItem>
                  <SidebarMenuButton active render={<a href="#home" />}>
                    <HouseIcon />
                    <span>Home</span>
                  </SidebarMenuButton>
                </SidebarMenuItem>
                <SidebarMenuItem>
                  <SidebarMenuButton render={<a href="#projects" />}>
                    <FolderIcon />
                    <span>Projects</span>
                  </SidebarMenuButton>
                </SidebarMenuItem>
              </SidebarMenu>
            </SidebarGroup>
          </SidebarContent>
          <SidebarSeparator />
          <SidebarFooter>
            <SidebarMenu>
              <SidebarMenuItem>
                <DropdownMenu>
                  <DropdownMenuTrigger
                    render={
                      <SidebarMenuButton
                        aria-label="Account, Mara Ellis"
                        size="xl"
                        type="button"
                      />
                    }
                  >
                    <Avatar aria-hidden="true">
                      <AvatarFallback>ME</AvatarFallback>
                    </Avatar>
                    <SidebarMenuButtonText>
                      <SidebarMenuButtonTitle>
                        Mara Ellis
                      </SidebarMenuButtonTitle>
                      <SidebarMenuButtonDescription>
                        mara.ellis@northlight.example.com
                      </SidebarMenuButtonDescription>
                    </SidebarMenuButtonText>
                    <ChevronsUpDownIcon />
                  </DropdownMenuTrigger>
                  <DropdownMenuContent
                    align="start"
                    className="w-56"
                    side="top"
                  >
                    <DropdownMenuItem>
                      <UserIcon />
                      Profile
                    </DropdownMenuItem>
                    <DropdownMenuItem>
                      <SettingsIcon />
                      Settings
                    </DropdownMenuItem>
                    <DropdownMenuSeparator />
                    <DropdownMenuItem>
                      <LogOutIcon />
                      Sign out
                    </DropdownMenuItem>
                  </DropdownMenuContent>
                </DropdownMenu>
              </SidebarMenuItem>
            </SidebarMenu>
          </SidebarFooter>
        </Sidebar>
        <SidebarInset>
          <header className="flex items-center gap-2 border-b p-3">
            <h1 className="font-semibold">Home</h1>
          </header>
          <div className="p-4 text-sm">Your workspace content.</div>
        </SidebarInset>
      </SidebarProvider>
    </div>
  );
}
```

## Right-to-left layouts

`side` is the inline start or end of the page. In a right-to-left page, `side="left"` puts the sidebar on the right, together with its gap, its rail and the sheet on small screens, and a resizable rail moves its edge the other way. Badges, group actions and the nested guide line sit at the row's inline end or start, collapsed-rail tooltips open toward the content, and the default trigger glyph mirrors.

## Persist the state

On every real change of the desktop state, the provider writes it to a cookie named `sidebar_state`, for seven days. It never reads the cookie back, so read it where the page renders and pass it to `defaultOpen`; the first render then matches the last choice. In Next.js, a nested layout for the pages with the sidebar can read it on the server:

```tsx title="src/app/(app)/layout.tsx"
import { cookies } from "next/headers";
import { SidebarProvider } from "@/siteplane/components/sidebar";

export default async function Layout({
  children,
}: {
  children: React.ReactNode;
}) {
  const cookieStore = await cookies();
  const defaultOpen = cookieStore.get("sidebar_state")?.value !== "false";
  return (
    <SidebarProvider defaultOpen={defaultOpen}>{children}</SidebarProvider>
  );
}
```

Set `stateCookie` to another name when several sidebars share a domain, for example an application sidebar and a settings sidebar, or to `false` to write no cookie.

## Controlled desktop state

open and onOpenChange control desktop expansion. Mobile sheet state remains separate.

Controlled desktop state

[Complete TSX file](/examples/sidebar/controlled.tsx)

```tsx
"use client";

import {
  Sidebar,
  SidebarBrand,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarGroupLabel,
  SidebarHeader,
  SidebarInset,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarProvider,
  SidebarTrigger,
} from "@/siteplane/components/sidebar";
import { FolderIcon, HomeIcon, SettingsIcon } from "lucide-react";
import { useState } from "react";

export default function Example() {
  const [active, setActive] = useState("Overview");
  const [open, setOpen] = useState(true);
  return (
    <div className="relative h-[32rem] w-full overflow-hidden [transform:translateZ(0)]">
      <SidebarProvider
        className="h-full min-h-0"
        open={open}
        onOpenChange={setOpen}
      >
        <Sidebar className="h-full">
          <SidebarHeader>
            <SidebarBrand>Acme</SidebarBrand>
          </SidebarHeader>
          <SidebarContent>
            <SidebarGroup>
              <SidebarGroupLabel>Workspace</SidebarGroupLabel>
              <SidebarMenu>
                {[
                  { label: "Overview", icon: HomeIcon },
                  { label: "Projects", icon: FolderIcon },
                  { label: "Settings", icon: SettingsIcon },
                ].map(({ label, icon: Icon }) => (
                  <SidebarMenuItem key={label}>
                    <SidebarMenuButton
                      tooltip={label}
                      active={active === label}
                      onClick={() => setActive(label)}
                    >
                      <Icon />
                      <span>{label}</span>
                    </SidebarMenuButton>
                  </SidebarMenuItem>
                ))}
              </SidebarMenu>
            </SidebarGroup>
          </SidebarContent>
          <SidebarFooter>
            <span className="truncate px-2 text-xs text-muted-foreground">
              Acme workspace
            </span>
          </SidebarFooter>
        </Sidebar>
        <SidebarInset>
          <header className="flex items-center gap-2 border-b p-3">
            <SidebarTrigger />
            <h1 className="font-semibold">{active}</h1>
          </header>
          <div className="p-4 text-sm">Your workspace content.</div>
        </SidebarInset>
      </SidebarProvider>
    </div>
  );
}
```

## API reference

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| SidebarProvider.layout | "inset" \| "push" | — | Application layout; DashboardShell sets it. |
| SidebarProvider.defaultOpen / open | boolean | true / uncontrolled | Initial or controlled desktop expansion. |
| SidebarProvider.mobileBreakpoint | "md" \| "lg" | "md" | Switch to the mobile Sheet below 768 px (md) or 1024 px (lg), the widths of the md: and lg: utilities. |
| SidebarProvider.stateCookie | string \| false | "sidebar_state" | Name of the cookie that keeps the desktop state, or false for no cookie. The provider writes it and never reads it. |
| SidebarProvider.keyboardShortcut | boolean | true | Register Ctrl/Cmd+B. Disable for an auxiliary navigation that must not compete with the application sidebar. |
| SidebarProvider.onOpenChange | (open: boolean) => void | — | Desktop expansion changes. |
| Sidebar side / variant / collapsible | left \| right / sidebar \| floating \| inset / offcanvas \| icon \| none | left / sidebar / offcanvas | Attachment, surface, and collapse behavior; none remains static on mobile. left and right are the inline start and end, so a left sidebar sits on the right in a right-to-left page. |
| Sidebar.mobileVariant | default \| fullscreen | default | Mobile navigation surface. default slides in from the sidebar's side at the mobile width (18rem); fullscreen fills the viewport and fades in from the top. |
| SidebarTrigger | Button props | ghost, square at the provider size (icon-lg) | Toggles desktop or mobile state and announces it with aria-expanded and aria-controls; invokes supplied onClick before toggling. |
| SidebarRail.resizable | boolean | false | Pointer resize within CSS width limits; rail is excluded from Tab navigation. Supplied onClick and onPointerDown run before the rail's own. |
| SidebarMenuItem.expandTrigger | boolean | false | Places SidebarTrigger over the item's row. On the collapsed icon rail, hover or focus swaps the row for the trigger, so a workspace mark also expands the sidebar. A SidebarBrand in the item steps aside the same way. |
| SidebarMenuItem.expandLabel | string | "Expand sidebar" | Accessible name of the expand trigger. |
| SidebarMenuButton.active / tooltip | boolean / string \| TooltipPopup props | false / — | active shows the active style and sets aria-current="page" unless you pass aria-current, which shows the active style as well. isActive is a deprecated alias of active. tooltip names the item on the collapsed desktop rail. |
| SidebarMenuButton.size / variant | sm \| md \| lg \| xl / default \| outline \| workspace | provider (lg) / default | Menu density and treatment. |
| SidebarMenuButton.render | Element or render callback | button | Router/link composition. |
| SidebarMenuButtonText / SidebarMenuButtonTitle / SidebarMenuButtonDescription | span props | — | A two-line row: the title in the row's text over a description one text step smaller in the tertiary color. Both truncate, and plain text keeps its full value in title unless you pass one. |
| SidebarMenuAction.showOnHover | boolean | false | Reveal action on hover/focus; accepts button useRender props. |
| SidebarMenuSkeleton.showIcon / width / size | boolean / string / "sm" \| "md" \| "lg" \| "xl" | false / "72%" / provider (lg) | Loading row shape, as high as a menu row of the same size. |
| SidebarMenuSubButton.size / active | "sm" \| "md" \| "lg" \| "xl" / boolean | provider (lg) / false | Nested button dimensions/state; active also sets aria-current="page", and a passed aria-current shows the active style. isActive is a deprecated alias of active. Accepts ButtonBaseProps except size. |
| SidebarGroupLabel / SidebarGroupAction | useRender div / button props | div / button | Group labels and actions with render composition. A plain-text label ends with an ellipsis and keeps its full text in title. |
| SidebarInset | main props | — | Application content beside the sidebar: the primary surface from the mobile breakpoint up, with a layout or without one. In the push layout it moves its content clear of the expanded sidebar. |
| SidebarBrand | useRender div props | div | The application's logo: its mark first, then its name or wordmark. It starts at the rows' 8 px icon inset and is as tall as the rail's 36 px square, reaching 2 px into the band's padding, so the band stays 48 px. On the collapsed icon rail only the mark shows, in that square, and keeps its place while the name fades out. A plain-text name ends with an ellipsis and keeps its full text in title. Render a link with render; in a SidebarMenuItem with expandTrigger it steps aside for the expand control on the rail. |
| SidebarHeader / SidebarFooter / SidebarContent / SidebarGroup / SidebarGroupContent / SidebarMenuBadge | div props | — | Layout parts; content supplies scrolling and space for the shared focus outline. Do not add a nested scrolling wrapper around menu groups. |
| SidebarMenu / SidebarMenuSub / SidebarMenuItem / SidebarMenuSubItem | ul / ul / li / li props | — | List semantics. |
| SidebarInput / SidebarSeparator | Input / Separator props | Kit defaults | Styled input, and the kit divider inside the sidebar's 8 px inset. |
| useSidebar() | SidebarContextProps | Provider required | layout, state, open, setOpen, openMobile, setOpenMobile, isMobile, mobileBreakpoint, toggleSidebar. |
| SidebarContext | React.Context<SidebarContextProps \| null> | null | Public context, normally read through useSidebar. |
| sidebarMenuPopupWidthClassName | string | "w-56" | Width utility for menus attached to the sidebar. |

`SidebarLayout` is the exported `"inset" | "push"` union used by the provider and context.

[Read the installed source](/source/sidebar).

## Usage notes

The provider registers Ctrl/Cmd+B unless keyboardShortcut is false; the shortcut leaves the keys to a focused text field or editor, to input methods and to handlers that already took the event. Desktop changes write the state cookie (path `/`) for seven days, `sidebar_state` unless `stateCookie` names another or turns it off, using the Cookie Store API where the browser provides it and `document.cookie` otherwise; if cookies are blocked, the sidebar still toggles. A controlled state is written once the parent applies the change. Initial state is not read back automatically: a server or application must read the cookie and pass defaultOpen, as in [Persist the state](#persist-the-state). A collapsed offcanvas sidebar leaves the tab order and the accessibility tree once it has slid out; its rail stays available to open it. Mobile navigation uses Sheet and starts closed; its dialog takes the Sidebar's `aria-label` as its title ("Sidebar" without one), and closing it returns focus to the SidebarTrigger that opened it. On desktop, `id`, `aria-label`, `aria-labelledby` and `aria-describedby` go to the `aside` landmark, while `className`, `style` and the other props go to the sidebar container; on mobile every prop reaches the sheet. Set `mobileVariant="fullscreen"` on `Sidebar` for a full-viewport navigation surface. Nested navigation pages can compose `Sheet` with `variant="fullscreen"`; the Sheet owns focus, Escape and return focus. Set mobileBreakpoint="lg" when a header switches to mobile navigation below 1024 px. SidebarTrigger accepts children to customize its icon; provide an accessible label when replacing the default content. `--sidebar-width` defaults to 16rem, icon width to 3.25rem, resize bounds to 12rem/24rem, and mobile width to 18rem. Set desktop custom properties through provider style. The bounded preview wrapper is included in the source; remove it for a full viewport application. Native element attributes and the linked [Button](/docs/components/button/), [Input](/docs/components/input/) and [Tooltip](/docs/components/tooltip/) APIs apply to their respective parts.

In the complete examples, workspace selection and the active navigation item update locally. Replace the sample data and hash links with your routes, and connect search, account, billing and project actions to your application. Keyboard shortcut labels are illustrative; only Ctrl/Cmd+B is registered by the sidebar provider.

The preview runs in its own document viewport so navigation landmarks and responsive behavior stay independent of the docs.
