Sidebar app shell
Sidebar with sections, nested items, badges and a user card.
Good morning, Mia
Tuesday 7 October · 3 requests need your review.
Headcount
248
+6 Increase, goodsince September
Out today
12
4 on annual leave, 8 remote
Open roles
9
-2 Decrease, goodsince September
Waiting for approval
sidebar-app-shell.tsx
"use client";
import { useEffect, useRef, useState } from "react";
import {
LuCalendarDays,
LuChartColumn,
LuClipboardCheck,
LuFileText,
LuLayoutDashboard,
LuLifeBuoy,
LuLogOut,
LuSettings,
LuTarget,
LuUser,
LuUsers,
LuWallet,
} from "react-icons/lu";
import {
Avatar,
Badge,
Button,
ListItem,
MenuDivider,
MenuItem,
MetricCard,
Sidebar,
SidebarItem,
SidebarSection,
SidebarSubItem,
SidebarUserCard,
} from "@nexera-ui/react";
// Sample data
interface Person {
id: string;
name: string;
initials: string;
role: string;
team: string;
email: string;
location: string;
status: "Active" | "On leave" | "Remote" | "Invited";
}
const PEOPLE: Person[] = [
{
id: "p1",
name: "Ayesha Khan",
initials: "AK",
role: "Senior product designer",
team: "Design",
email: "ayesha.khan@northwind.io",
location: "London",
status: "Active",
},
{
id: "p2",
name: "Sara Malik",
initials: "SM",
role: "Payroll specialist",
team: "People Ops",
email: "sara.malik@northwind.io",
location: "Lisbon",
status: "On leave",
},
{
id: "p3",
name: "Ahmed Raza",
initials: "AR",
role: "Engineering manager",
team: "Engineering",
email: "ahmed.raza@northwind.io",
location: "Lahore",
status: "Remote",
},
{
id: "p4",
name: "Mia Jensen",
initials: "MJ",
role: "Product manager",
team: "Product",
email: "mia.jensen@northwind.io",
location: "Copenhagen",
status: "Active",
},
{
id: "p5",
name: "Luis Ortega",
initials: "LO",
role: "Frontend engineer",
team: "Engineering",
email: "luis.ortega@northwind.io",
location: "Madrid",
status: "Active",
},
{
id: "p6",
name: "Yuki Tanaka",
initials: "YT",
role: "Recruiter",
team: "People Ops",
email: "yuki.tanaka@northwind.io",
location: "Tokyo",
status: "Active",
},
{
id: "p7",
name: "Chloe Martin",
initials: "CM",
role: "Data analyst",
team: "Finance",
email: "chloe.martin@northwind.io",
location: "Paris",
status: "Remote",
},
{
id: "p8",
name: "Omar Haddad",
initials: "OH",
role: "Support lead",
team: "Customer",
email: "omar.haddad@northwind.io",
location: "Dubai",
status: "Invited",
},
];
const APPROVALS = [
{
person: PEOPLE[1],
request: "Annual leave · 14 to 18 Oct",
tone: "leave" as const,
label: "Leave",
},
{
person: PEOPLE[4],
request: "Expense claim · €212.40",
tone: "info" as const,
label: "Expense",
},
{
person: PEOPLE[6],
request: "Remote work · 21 to 25 Oct",
tone: "remote" as const,
label: "Remote",
},
].flatMap(({ person, ...rest }) => (person ? [{ person, ...rest }] : []));
/** Sidebar app shell: sections, nested items, badges and a user card, with a dashboard in the main area. */
export default function SidebarAppShell() {
const rootRef = useRef<HTMLDivElement>(null);
// The rail below 1024 px of container width, expanded above; the toggle still works in between resizes.
const [isCollapsed, setCollapsed] = useState(true);
useEffect(() => {
const node = rootRef.current;
if (!node) return;
const observer = new ResizeObserver(([entry]) => {
setCollapsed((entry?.contentRect.width ?? 0) < 1024);
});
observer.observe(node);
return () => {
observer.disconnect();
};
}, []);
return (
<div ref={rootRef} className="@container bg-page px-4 py-8 @3xl:px-8">
<div className="flex min-h-[640px] overflow-hidden rounded-xl border border-default bg-page shadow-sm">
<Sidebar
aria-label="Northwind"
className="h-auto self-stretch"
isCollapsed={isCollapsed}
onCollapsedChange={setCollapsed}
skipLinkHref="#shell-main"
footer={
<>
<SidebarItem href="#help" icon={<LuLifeBuoy />} label="Help and support" />
<SidebarItem href="#settings" icon={<LuSettings />} label="Settings" />
</>
}
userCard={
<SidebarUserCard
name="Mia Jensen"
email="mia.jensen@northwind.io"
initials="MJ"
presence="online"
>
<MenuItem id="profile" label="Profile" icon={<LuUser />} />
<MenuItem id="account" label="Account settings" icon={<LuSettings />} />
<MenuDivider />
<MenuItem id="sign-out" label="Sign out" icon={<LuLogOut />} />
</SidebarUserCard>
}
>
<SidebarItem href="#dashboard" icon={<LuLayoutDashboard />} label="Dashboard" isCurrent />
<SidebarSection label="Workspace">
<SidebarItem icon={<LuUsers />} label="People" defaultExpanded>
<SidebarSubItem href="#directory" label="Directory" />
<SidebarSubItem href="#teams" label="Teams" />
<SidebarSubItem href="#org-chart" label="Org chart" />
</SidebarItem>
<SidebarItem href="#leave" icon={<LuCalendarDays />} label="Leave and attendance" />
<SidebarItem href="#payroll" icon={<LuWallet />} label="Payroll" />
<SidebarItem
href="#approvals"
icon={<LuClipboardCheck />}
label="Approvals"
badge="3"
badgeLabel="3 pending"
/>
<SidebarItem href="#documents" icon={<LuFileText />} label="Documents" />
</SidebarSection>
<SidebarSection label="Insights">
<SidebarItem href="#reports" icon={<LuChartColumn />} label="Reports" />
<SidebarItem href="#performance" icon={<LuTarget />} label="Performance" />
</SidebarSection>
</Sidebar>
<div id="shell-main" tabIndex={-1} className="min-w-0 flex-1 p-4 outline-none @3xl:p-8">
<div className="mx-auto grid max-w-4xl gap-6">
<header className="flex flex-wrap items-end justify-between gap-3">
<div className="grid gap-1">
<h2 className="text-heading-h2 text-primary">Good morning, Mia</h2>
<p className="text-body-default text-secondary">
Tuesday 7 October · 3 requests need your review.
</p>
</div>
<Button variant="secondary" size="md">
Export report
</Button>
</header>
<div className="grid gap-4 @3xl:grid-cols-3">
<MetricCard
label="Headcount"
value="248"
type="trend"
delta="+6"
trend="up-good"
caption="since September"
/>
<MetricCard label="Out today" value="12" caption="4 on annual leave, 8 remote" />
<MetricCard
label="Open roles"
value="9"
type="trend"
delta="-2"
trend="down-good"
caption="since September"
/>
</div>
<section aria-labelledby="sidebar-shell-approvals" className="grid gap-3">
<h3 id="sidebar-shell-approvals" className="text-heading-h4 text-primary">
Waiting for approval
</h3>
<ul className="m-0 list-none overflow-hidden rounded-lg border border-default bg-surface p-0">
{APPROVALS.map(({ person, request, tone, label }) => (
<li key={person.id} className="border-b border-subtle last:border-b-0">
<ListItem
href={`#approval-${person.id}`}
title={person.name}
subtitle={request}
leading={<Avatar size="lg" initials={person.initials} isDecorative />}
trailing={<Badge tone={tone} label={label} />}
/>
</li>
))}
</ul>
</section>
</div>
</div>
</div>
</div>
);
}
More app shells and navigation blocks
- Top bar with tabsPage title, breadcrumbs, actions and section tabs.
- Mobile app shellTop app bar and a bottom navigation bar.
- Settings layoutVertical tabs on the left, a form on the right.
- Command palette⌘K search for pages, people and actions.
- Page headerTitle, description, status badge and primary actions.
- Workspace switcherSwitch organisation or create a new one.