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-insetRequires the @cosskit namespace in your components.json.
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's what'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