Cosskit

Inset Sidebar App Shell

Application shell with an inset sidebar, workspace switcher, notifications, and a content area ready for page composition.

pnpm dlx shadcn@latest add @cosskit/app-shell-inset

Requires the @cosskit namespace in your components.json.

Open full page

Code

"use client";

import {
  BellIcon,
  BuildingIcon,
  ChartNoAxesColumnIncreasingIcon,
  ChevronsUpDownIcon,
  ChevronUpIcon,
  FolderKanbanIcon,
  InboxIcon,
  LayoutDashboardIcon,
  LifeBuoyIcon,
  PlusIcon,
  SettingsIcon,
  UserPlusIcon,
  UsersIcon,
} from "lucide-react";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import {
  Menu,
  MenuPopup,
  MenuSeparator,
  MenuTrigger,
  MenuItem,
} from "@/components/ui/menu";
import { Separator } from "@/components/ui/separator";
import {
  Sidebar,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarGroupContent,
  SidebarGroupLabel,
  SidebarHeader,
  SidebarInset,
  SidebarMenu,
  SidebarMenuBadge,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarProvider,
  SidebarRail,
  SidebarTrigger,
} from "@/components/ui/sidebar";

const platformNav = [
  { icon: LayoutDashboardIcon, label: "Dashboard", href: "#", isActive: true },
  { icon: InboxIcon, label: "Inbox", href: "#", badge: "12" },
  { icon: UsersIcon, label: "Customers", href: "#" },
  { icon: FolderKanbanIcon, label: "Projects", href: "#" },
  { icon: ChartNoAxesColumnIncreasingIcon, label: "Analytics", href: "#" },
];

const workspaceNav = [
  { icon: SettingsIcon, label: "Settings", href: "#" },
  { icon: UserPlusIcon, label: "Members", href: "#" },
  { icon: LifeBuoyIcon, label: "Support", href: "#" },
];

export default function AppShellInset() {
  return (
    <SidebarProvider className="[&_[data-slot=button]]:transition-[box-shadow,scale] [&_[data-slot=button]:not(.w-full)]:motion-safe:hover:scale-[1.05] [&_[data-slot=button].w-full]:motion-safe:hover:scale-[1.03]">
      <Sidebar variant="inset" collapsible="icon">
        {/* Workspace switcher. `tooltip` is not decoration here — this sidebar
            collapses to an icon rail, where the label and chevron are hidden and
            the tooltip is the only thing naming the workspace. */}
        <SidebarHeader>
          <SidebarMenu>
            <SidebarMenuItem>
              <Menu>
                <MenuTrigger
                  render={
                    <SidebarMenuButton
                      size="lg"
                      tooltip="Northwind"
                      className="cursor-pointer data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
                    />
                  }
                >
                  <div className="flex size-8 shrink-0 items-center justify-center rounded-lg bg-primary text-primary-foreground font-bold text-sm">
                    N
                  </div>
                  <div className="flex min-w-0 flex-col leading-tight">
                    <span className="truncate font-semibold text-sm">
                      Northwind
                    </span>
                    <span className="truncate text-xs text-muted-foreground">
                      Workspace
                    </span>
                  </div>
                  <ChevronsUpDownIcon className="ms-auto size-4 shrink-0" />
                </MenuTrigger>
                {/* Pointer cursor is a house rule; coss ships `cursor-default`
                    here by native-menu convention. It goes on the popup — which
                    portals out of this block, so no ancestor rule can reach it —
                    and never by editing coss source. The `-item` suffix covers
                    checkbox and radio items too, so later additions inherit it. */}
                <MenuPopup
                  align="start"
                  className="min-w-56 [&_[data-slot$='-item']]:cursor-pointer"
                  sideOffset={8}
                >
                  <MenuItem>
                    <BuildingIcon />
                    Northwind
                  </MenuItem>
                  <MenuItem>
                    <BuildingIcon />
                    Harborview Media
                  </MenuItem>
                  <MenuSeparator />
                  <MenuItem>
                    <PlusIcon />
                    New workspace
                  </MenuItem>
                </MenuPopup>
              </Menu>
            </SidebarMenuItem>
          </SidebarMenu>
        </SidebarHeader>

        {/* Nav groups */}
        <SidebarContent>
          {/* Platform group */}
          <SidebarGroup>
            <SidebarGroupLabel>Platform</SidebarGroupLabel>
            <SidebarGroupContent>
              <SidebarMenu>
                {platformNav.map(({ icon: Icon, label, href, isActive, badge }) => (
                  <SidebarMenuItem key={label}>
                    <SidebarMenuButton
                      isActive={isActive}
                      tooltip={label}
                      render={<a href={href} />}
                    >
                      <Icon />
                      <span>{label}</span>
                    </SidebarMenuButton>
                    {badge && (
                      <SidebarMenuBadge>{badge}</SidebarMenuBadge>
                    )}
                  </SidebarMenuItem>
                ))}
              </SidebarMenu>
            </SidebarGroupContent>
          </SidebarGroup>

          {/* Workspace group */}
          <SidebarGroup>
            <SidebarGroupLabel>Workspace</SidebarGroupLabel>
            <SidebarGroupContent>
              <SidebarMenu>
                {workspaceNav.map(({ icon: Icon, label, href }) => (
                  <SidebarMenuItem key={label}>
                    <SidebarMenuButton
                      tooltip={label}
                      render={<a href={href} />}
                    >
                      <Icon />
                      <span>{label}</span>
                    </SidebarMenuButton>
                  </SidebarMenuItem>
                ))}
              </SidebarMenu>
            </SidebarGroupContent>
          </SidebarGroup>
        </SidebarContent>

        {/* User footer with dropdown menu */}
        <SidebarFooter>
          <SidebarMenu>
            <SidebarMenuItem>
              <Menu>
                <MenuTrigger
                  render={
                    <SidebarMenuButton
                      size="lg"
                      tooltip="Ava Chen"
                      className="cursor-pointer data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
                    />
                  }
                >
                  <Avatar className="size-8 rounded-lg">
                    {/* Swap in <AvatarImage src="..." alt="Ava Chen" /> when you
                        have a real avatar URL. */}
                    <AvatarFallback className="rounded-lg">AC</AvatarFallback>
                  </Avatar>
                  <div className="flex flex-col leading-tight min-w-0">
                    <span className="font-medium text-sm truncate">Ava Chen</span>
                    <span className="text-xs text-muted-foreground truncate">
                      ava@northwind.co
                    </span>
                  </div>
                  <ChevronUpIcon className="ms-auto size-4 shrink-0" />
                </MenuTrigger>
                <MenuPopup
                  side="top"
                  align="end"
                  sideOffset={8}
                  className="[&_[data-slot$='-item']]:cursor-pointer"
                >
                  <MenuItem>Account</MenuItem>
                  <MenuItem>Billing</MenuItem>
                  <MenuSeparator />
                  <MenuItem variant="destructive">Sign out</MenuItem>
                </MenuPopup>
              </Menu>
            </SidebarMenuItem>
          </SidebarMenu>
        </SidebarFooter>

        <SidebarRail />
      </Sidebar>

      {/* Main content area */}
      <SidebarInset>
        {/* Sticky top header bar */}
        <header className="sticky top-0 z-10 flex h-14 shrink-0 items-center gap-2 border-b bg-background px-4">
          <SidebarTrigger className="-ms-1" />
          <Separator orientation="vertical" className="h-5" />
          <span className="font-medium text-sm">Dashboard</span>

          {/* The dot is positioned, not inlined, so the button stays a square
              icon target and the badge never shifts the header's layout. */}
          <Button
            aria-label="Notifications — 3 unread"
            className="relative ms-auto"
            size="icon-sm"
            variant="ghost"
          >
            <BellIcon />
            <span className="absolute end-1.5 top-1.5 size-1.5 rounded-full bg-primary" />
          </Button>
        </header>

        {/* Page content */}
        <div className="flex flex-1 flex-col gap-6 p-6">
          <div className="flex flex-col gap-1">
            <h1 className="text-2xl font-semibold tracking-tight">
              Good morning, Ava
            </h1>
            <p className="text-muted-foreground text-sm">
              Here&apos;s what&apos;s happening across your workspace today.
            </p>
          </div>

          {/* Placeholder content area */}
          <div className="flex flex-1 min-h-80 items-center justify-center rounded-xl border border-dashed">
            <div className="flex flex-col items-center gap-2 text-center">
              <LayoutDashboardIcon className="size-8 text-muted-foreground/50" />
              <p className="text-sm font-medium text-muted-foreground">
                Your page content goes here
              </p>
              <p className="text-xs text-muted-foreground/70 max-w-xs">
                Drop blocks into this area to compose your dashboard — charts,
                tables, KPI cards, and more.
              </p>
            </div>
          </div>
        </div>
      </SidebarInset>
    </SidebarProvider>
  );
}

Dependencies

  • @coss/ui