UX Standards — 01: Layout Shell
Governs: The full-page layout structure every dashboard page lives inside.
Parent rules: See 00-OVERVIEW-AND-CSS-RULES.md first.
Visual Anatomy
The sidebar is a full-height left rail that spans the entire viewport from top to bottom. The header, content area, and footer live entirely within the right column (SidebarInset) — they never span over the sidebar.
Note the bottom alignment: the sidebar's user card and the footer in the right column sit at the same vertical level — both pinned to the bottom of their respective columns.
Expanded State (w-64 — default)
┌────────────────────┬──────────────────────────────────────────────────────┐
│ SIDEBAR (w-64) │ HEADER (sticky, h-16, border-b bg-background) │
│ │ [Page Title] [Subtitle] [Co.Logo][Anna][Srch][More] │
│ ┌────────────────┐ ├──────────────────────────────────────────────────────┤
│ │ App Name │ │ MAIN CONTENT AREA (flex-1, overflow-y-auto, p-6) │
│ │ App subtitle ⌄ │ │ ┌──────────────────────────────────────────────────┐ │
│ └────────────────┘ │ │ [Stats Cards — optional] │ │
│ ───────────────── │ ├──────────────────────────────────────────────────┤ │
│ [ico] Nav item │ │ [Tab Navigation — optional] │ │
│ [ico] Nav item │ ├──────────────────────────────────────────────────┤ │
│ [ico] Nav item ▶ │ │ [UDT Toolbar] │ │
│ (scrollable) │ ├──────────────────────────────────────────────────┤ │
│ · │ │ [Universal Data Table] (flex-1, min-h-0) │ │
│ · │ └──────────────────────────────────────────────────┘ │
│ ───────────────── ├──────────────────────────────────────────────────────┤
│ Username │ FOOTER (h-12, border-t) │
│ user@email... ⌄ │ [N row(s) total.] [← ‹ 1 › → ][Page 1 of N][Rows] │
└────────────── ──────┴──────────────────────────────────────────────────────┘
Note: The user card at the bottom of the sidebar and the footer in the right column are horizontally aligned — both anchored to the bottom of the viewport.
Collapsed State (w-16 — icons only)
When collapsed, only icons are visible — no text. The app switcher trigger contracts from the app name row to the shield icon only. Nav items show icon-only with tooltip on hover.
┌──────┬────────────────────────────────────────────────────────────────────┐
│ w16 │ HEADER (sticky, h-16, border-b bg-background) │
│ │ [Page Title] [Subtitle] [Co.Logo][Anna][Srch][Theme][More] │
│ [🛡] ├────────────────────────────────────────────────────────────────────┤
│ ──── │ MAIN CONTENT AREA (flex-1, overflow-y-auto, p-6) │
│ [ico]│ ┌──────────────────────────────────────────────────────────────┐ │
│ [ico]│ │ [Stats Cards — optional] │ │
│ [ico]│ ├──────────────────────────────────────────────────────────────┤ │
│ · │ │ [Tab Navigation — optional] │ │
│ · │ ├──────────────────────────────────────────────────────────────┤ │
│ ──── │ │ [UDT Toolbar] │ │
│ [SU] ├───────────────────────────────── ─────────────────────────────────┤ │
│ │ FOOTER (h-12, border-t) │ │
│ │ [N row(s) total.] [← ‹ 1 › →][Page 1 of N][Rows ▾] │ │
└──────┴────────────────────────────────────────────────────────────────────┘
App Switcher (accessible in both states)
The app switcher lives at the top of the sidebar, not in the header. Clicking the trigger opens a dropdown listing all Anshin applications.
┌───────────────────────┐
│ Anshin Applications │ ← DropdownMenuLabel
│ ───────────────── │
│ ✓ Anshin Orchestrate │ ← current app (checkmark)
│ Anshin MCP │
│ Anshin Comply │
│ Anshin Predict │
│ Anshin Admin Portal │
│ Anshin Client Portal│
│ Anshin Engage │
│ ───────────────── │
│ [Settings] → │ ← workspace settings
└───────────────────────┘
| Sidebar state | Switcher trigger | Dropdown anchors to |
|---|---|---|
| Expanded | [🛡 App Name / subtitle ⌄] row at top | Below the trigger row |
| Collapsed | Shield icon only at top | Below/beside the shield |
Detailed app switcher spec → see 17-APPLICATION-SWITCHER.md
Critical layout rules:
- Header and footer are always inside
SidebarInset(right column). They never span the full page width or overlap the sidebar. - The app switcher is in the sidebar header, not the top header bar.
- Footer and sidebar user card bottom-align at the same horizontal level.
Layer-by-Layer Specification
Outer Container
Orchestrate (canonical pattern) — SidebarProvider
// dashboard-layout-client.tsx
<SidebarProvider defaultOpen={true}>
<AppSidebar user={user} collapsible="icon" /> {/* full-height left rail */}
<SidebarInset> {/* right column — flex-col */}
<Header ... /> {/* h-16, top of right column */}
<main className="flex-1 overflow-y-auto overflow-x-hidden p-6">{children}</main>
<Footer /> {/* h-12, bottom of right column */}
</SidebarInset>
</SidebarProvider>
SidebarProvider handles all sizing, collapse state, and mobile sheet behavior automatically. Do not add an outer <div> wrapper around it.
Accelerate Modules (custom pattern) — <div> wrapper
// Used by fax-automation, referral-automation, and other Accelerate modules
<div className="min-h-screen w-full bg-background text-foreground flex flex-col lg:flex-row">
{/* sidebar + main content */}
</div>
min-h-screen— always full viewport heightw-full— full widthbg-background— respects dark/light themeflex flex-col lg:flex-row— vertical stacking on mobile, horizontal on desktop
Sidebar Zone
The sidebar is a full-height left rail — it spans from the very top to the very bottom of the viewport, independent of the header and footer. The header and footer live inside SidebarInset (the right column), not above/below the sidebar.
Collapse Behavior
| State | Sidebar width | Logo | Nav items |
|---|---|---|---|
| Expanded | w-64 (256px) | Full logo (shield + wordmark) | Icon + text label |
| Collapsed | w-16 (64px) | Shield icon only (no wordmark) | Icon only (no text) — tooltip on hover |
collapsible="icon"— shadcn sidebar prop that switches between expanded and icon-only- The sidebar never fully disappears on desktop — it contracts to
w-16, not hidden - On mobile (
< lg): sidebar hides entirely; hamburger →Sheetslide-over
Sidebar Internal Structure (top → bottom)
- App Switcher trigger (
SidebarHeader) — expanded:[🛡 App Name / subtitle ⌄]; collapsed: shield icon only. Clicking opens the application switcher dropdown. - Navigation groups (
SidebarContent) — scrollable area; nav items with icons + text labels (expanded) or icons only with tooltips (collapsed) - User card (
SidebarFooter) — pinned to bottom;avatar + name + email [⌄](expanded) or avatar-only (collapsed)
Detailed sidebar spec → see 03-SIDEBAR-NAVIGATION.md
Header Zone
The header lives inside SidebarInset — it is a right-column element only. It does NOT span the sidebar.
<header className="sticky top-0 z-50 flex h-16 w-full shrink-0 items-center justify-between gap-2 border-b bg-background px-4">
sticky top-0 z-50— always visible, above all contenth-16— exactly 64px. Non-negotiable.border-b bg-background— consistent separationpx-4— horizontal padding
Detailed header spec → see 02-HEADER.md
Main Content Area
<main className="flex-1 overflow-y-auto overflow-x-hidden p-6">
{children}
</main>
flex-1— fills all remaining space after sidebaroverflow-y-auto— content scrolls, not the pageoverflow-x-hidden— prevents horizontal scroll at page level (table uses internal scroll)p-6— 24px padding on all sides. This is the standard content padding.
Special cases:
- Full-screen/full-canvas pages (e.g., chat, workflow builder): use
p-0instead - Mobile: padding reduces to
p-4
Footer Zone
The footer lives inside SidebarInset — it is a right-column element only. It does NOT span the sidebar.
<footer className="flex h-12 shrink-0 items-center justify-between border-t bg-background px-6 text-sm text-muted-foreground">
h-12— exactly 48px. Non-negotiable.shrink-0— never compressedborder-t bg-background— consistent separationpx-6— horizontal padding (slightly wider than header)
Detailed footer spec → see 13-FOOTER.md
Component Wiring — Orchestrate
// packages/shared/components/layouts/header.tsx
// packages/shared/components/layouts/app-sidebar.tsx
// packages/shared/components/layouts/footer.tsx
// dashboard-layout-client.tsx:
<SidebarProvider defaultOpen={true}>
<AppSidebar user={user} collapsible='icon' />
<SidebarInset>
<Header user={user} fetchCompanyLogo={fetchCompanyLogoFn} notificationSlot={<NotificationPopover />} />
<main className="flex-1 overflow-y-auto overflow-x-hidden p-6">
{children}
</main>
<Footer />
</SidebarInset>
</SidebarProvider>
Component Wiring — Accelerate Modules
// modules/fax-automation/app/web-fax-dashboard/components/dashboard-layout.tsx
// modules/referral-automation/web-outreach-dashboard/components/ (layout TBD)
<div className="min-h-screen w-full bg-background text-foreground flex flex-col lg:flex-row">
{/* Mobile header (lg:hidden) */}
<header className="lg:hidden flex items-center justify-between px-4 py-3 border-b bg-card">
<Sheet>...</Sheet> {/* Hamburger menu → sidebar in Sheet */}
<span className="font-semibold text-lg">{APP_NAME}</span>
<NotificationBell />
</header>
{/* Desktop sidebar */}
<aside className="hidden lg:flex flex-col w-64 border-r bg-card shrink-0">
{/* Logo header */}
{/* Nav content */}
</aside>
<main className="flex-1 flex flex-col overflow-hidden">
{children}
</main>
</div>
Mobile Breakpoint Behavior
| Element | Mobile (< lg) | Desktop (lg+) |
|---|---|---|
| Sidebar | Hidden; hamburger → Sheet slide-over | Fixed 256px left column |
| Header | Simplified: hamburger + app name + bell | Full three-zone header |
| Content padding | p-4 | p-6 |
| Footer | Hidden (optional) | h-12 always visible |
| Tables | Horizontal scroll within overflow-x-auto wrapper | Full width |
When to Create a New Layout Shell
Never create a new layout from scratch. Always extend the existing shared layout. If a new module needs:
- A different sidebar structure → add a new
<aside>variant to the shared layout using avariantprop - No sidebar (e.g., a kiosk view) → use
className="flex-1"on main without the aside - A popup/detached window → use the popup layout (see
12-POPOUT-AND-SLIDE-OVER-WINDOWS.md)
Page Content Inner Structure
Within the <main> content area, every page follows this exact vertical stack:
[Stats Cards] ← optional, above tabs
[Tab Navigation] ← optional, full-width
[UDT Toolbar] ← always present on list pages
[Data Table] ← flex-1, fills remaining space
<div className="flex h-full flex-col space-y-4">
{/* 1. Stats cards (optional) */}
{stats && <div className={`grid gap-4 ${getGridCols(stats.length)}`}>...</div>}
{/* 2. Tab navigation (optional) — see 05-TAB-NAVIGATION.md */}
{preToolbarContent}
{/* 3. Toolbar (see 08-UDT-TOOLBAR.md) */}
<DataTableToolbar ... />
{/* 4. Table (flex-1 fills the rest) */}
<div className="flex min-h-0 flex-1 flex-col">
<DataTableSortable ... />
</div>
</div>
space-y-4 (16px gap) between all major zones. Do not use margin or padding for zone separation — always space-y-4 on the parent.
Violation Checklist
Before submitting any layout change, verify:
- Orchestrate: Outer container is
SidebarProvider— NOT a<div>wrapper - Accelerate: Outer container uses
min-h-screen w-full bg-background text-foreground - Sidebar is a full-height left rail —
AppSidebaror<aside w-64 border-r bg-card shrink-0> - Header is inside
SidebarInset— NOT a sibling ofAppSidebar - Footer is inside
SidebarInset— NOT at root level spanning sidebar - Header is exactly
h-16 sticky top-0 z-50 border-b bg-background - Footer is exactly
h-12 border-t bg-background - Main content uses
p-6(not p-4, p-8, or px-6 py-4) - No hardcoded colors in any layout wrapper
- Sidebar is
hidden lg:flex(not always visible, not always hidden) -
flex-1 overflow-y-auto overflow-x-hiddenon main content - Inner content stack uses
flex h-full flex-col space-y-4 - App switcher trigger is in
SidebarHeader(top of sidebar) — NOT in the header bar - Sidebar user card and footer are visually bottom-aligned at the same level