A sidebar + header application shell that wraps page content — the chrome the (app) route group uses for dashboard pages.
Install with the CLI, or add the packages and paste the source below.
Add App Shell to your app with its source-owned dependencies.
npx cronus-ui add app-shell-chrome
Install the Cronus UI packages, then paste the block below.
Copy the source for App Shell. Every class is a semantic token, so it re-themes with your app.
"use client";import {AppShell,Avatar,AvatarFallback,AvatarImage,Button,InviteDialog,Sidebar,SidebarContent,SidebarFooter,SidebarGroup,SidebarGroupContent,SidebarGroupLabel,SidebarHeader,SidebarMenu,SidebarMenuButton,SidebarMenuItem,SidebarSeparator,SidebarTrigger,WorkspaceSwitcher,} from "@cronus-ui/ui";import { TEAM, USER } from "../lib/demo-saas.js";import { Bell, Hexagon } from "lucide-react";import { type ReactNode, useState } from "react";/* @cronus:data app-nav */const APP_NAV = [{ label: "Dashboard", href: "/dashboard" },{ label: "Analytics", href: "/analytics" },{ label: "Team", href: "/team" },{ label: "Billing", href: "/billing" },{ label: "Settings", href: "/settings" },];/* @cronus:data-end */const OWNER = TEAM.find((m) => m.email === USER.email);const WORKSPACES = [{ id: "cronus", name: "Cronus", initials: "CR" },{ id: "northwind", name: "Northwind", initials: "NW" },{ id: "acme", name: "Acme", initials: "AC" },];export function AppShellChromeBlock({ children }: { children: ReactNode }) {const [workspaceId, setWorkspaceId] = useState("cronus");const sidebar = (<Sidebar collapsible="none" aria-label="Primary"><SidebarHeader><div className="flex items-center gap-2 px-2 py-1.5"><span className="inline-flex size-8 items-center justify-center rounded-lg bg-gradient-primary text-primary-foreground shadow-glow"><Hexagon className="size-4" aria-hidden="true" /></span><span className="truncate font-display text-sm font-semibold text-fg">Cronus</span></div><WorkspaceSwitcherworkspaces={WORKSPACES}value={workspaceId}onValueChange={setWorkspaceId}/></SidebarHeader><SidebarSeparator /><SidebarContent><SidebarGroup><SidebarGroupLabel>Menu</SidebarGroupLabel><SidebarGroupContent><SidebarMenu>{APP_NAV.map((item, index) => (<SidebarMenuItem key={item.href}><SidebarMenuButton asChild isActive={index === 0}><a href={item.href}><span>{item.label}</span></a></SidebarMenuButton></SidebarMenuItem>))}</SidebarMenu></SidebarGroupContent></SidebarGroup></SidebarContent><SidebarFooter><div className="flex items-center gap-2 rounded-lg px-2 py-1.5"><Avatar className="size-8">{OWNER?.avatar ? <AvatarImage src={OWNER.avatar} alt={USER.name} /> : null}<AvatarFallback>{USER.initials}</AvatarFallback></Avatar><div className="flex min-w-0 flex-col"><span className="truncate text-sm font-medium text-fg">{USER.name}</span><span className="truncate text-xs text-fg-tertiary">{USER.email}</span></div></div></SidebarFooter></Sidebar>);const header = (<div className="flex w-full items-center gap-3"><SidebarTrigger className="md:hidden" /><span className="font-display text-sm font-semibold text-fg">Cronus</span><div className="ms-auto flex items-center gap-2"><InviteDialogtrigger={<Button variant="outline" size="sm">Invite</Button>}/><Button variant="ghost" size="icon-sm" aria-label="Notifications"><Bell className="size-4" aria-hidden="true" /></Button><Avatar className="size-8">{OWNER?.avatar ? <AvatarImage src={OWNER.avatar} alt={USER.name} /> : null}<AvatarFallback>{USER.initials}</AvatarFallback></Avatar></div></div>);return (<AppShell sidebar={sidebar} header={header} providerProps={{ enableKeyboardShortcut: false }}><div className="flex flex-1 flex-col">{children}</div></AppShell>);}