Grid Architecture
The Verbiage
The Why
Fixed positioning causes layout overlaps because it removes elements from the document flow. When a sidebar uses position: fixed, it no longer occupies space in the layout, causing the main content to slide underneath it. This requires padding hacks like pl-64 to compensate, which break on different screen sizes and create maintenance nightmares.
We use a CSS Grid Shell to ensure the sidebar and main content never collide. CSS Grid creates dedicated tracks that each element occupies, preventing any overlap regardless of content size or screen width.
The Verbiage
Grid Track: A column in the CSS Grid layout. The first track is 260px wide for the sidebar, the second track (1fr) takes the remaining space for main content. Each track is a reserved space that no other element can invade.
Sidebar Constraint: The sidebar is constrained to its 260px grid track using lg:sticky positioning, not lg:fixed. This keeps it within the document flow while remaining visible during scroll. The sticky positioning respects the grid track boundaries.
Fluid Main: The main content area uses 1fr (fractional unit) to fill all remaining horizontal space after the sidebar track, creating a responsive layout that adapts to any screen width. The 1fr unit means "take all available space" after fixed-width tracks are calculated.
This pattern ensures zero overlap because each element has a reserved grid track, and the browser's grid engine handles all spacing automatically—no padding hacks needed.
Mobile Navigation Hierarchy
On mobile devices, we decouple Global Navigation (Header) from Contextual Navigation (Sidebar) to prevent "Navigation Overload"—a state where users are confused by multiple menus serving different purposes.
Global Navigation (Header): The top navigation bar contains site-wide links (Home, Library) and the search command palette. This is always accessible and serves as the primary way to move between major sections of the site.
Contextual Navigation (Sidebar): On library pages, the sidebar contains volume-specific chapter navigation. On mobile, this is hidden by default and accessed through the LibraryMobileNav component—a sticky "Browse Chapters" bar that appears below the header.
The Mobile Trigger: The LibraryMobileNav component provides a dedicated trigger for mobile chapter switching. It appears as a high-end technical document header (sticky, below the main header) with a "Browse Chapters" button that opens a drawer containing the volume switcher and chapter list.
Why This Matters: By separating global navigation from contextual navigation, users on mobile devices can immediately see content without sidebar interference. The header handles site-wide navigation, while the "Browse Chapters" bar handles volume-specific navigation. This clear separation prevents confusion and creates a more focused reading experience.
Desktop Behavior: On desktop (lg: breakpoint), the sidebar becomes visible in the grid layout, providing persistent access to chapter navigation without requiring a drawer.
The Law of the Constant Header
The Rule: The Global Header must never be unmounted or hidden by sub-navigation.
The Problem: "User Trapping" occurs when a visitor cannot find their way back to the homepage or other major sections of the site. This happens when contextual navigation (like a library sidebar) replaces or hides the global navigation header.
The Implementation:
1. Root Layout Placement: The Global Header is placed in app/layout.tsx at the root level, ensuring it persists across all routes and cannot be conditionally unmounted by page-specific layouts.
2. Z-Index Stacking:
- Global Header: z-50 (highest priority)
- Contextual Navigation (LibraryMobileNav): z-40 (below global)
- This ensures the global header always appears above contextual navigation elements.
3. Sticky Positioning: The Global Header uses sticky top-0 to remain visible during scroll, providing a constant anchor point for users.
The Reason: To prevent "User Trapping" where a visitor cannot find their way back to the homepage. The Global Header serves as the universal escape hatch—always visible, always accessible, regardless of how deep a user navigates into contextual content.
Violations: - ❌ Conditionally hiding the header based on route - ❌ Placing the header in a page-specific layout instead of root layout - ❌ Using z-index values that allow contextual navigation to cover the header - ❌ Unmounting the header component on specific routes
The Blueprint
// components/layout-wrapper.tsx
"use client";
import { usePathname } from "next/navigation";
import { Sidebar } from "@/components/sidebar";
import { LibrarySidebarNav } from "@/components/library-sidebar-nav";
import { LibraryMobileNav } from "@/components/library-mobile-nav";
import { SidebarProvider, useSidebar } from "@/components/sidebar-context";
import { Header } from "@/components/header";
import { getSidebarConfig } from "@/lib/sidebar-config";
function LayoutContent({ children }: { children: React.ReactNode }) {
const { sidebarOpen, setSidebarOpen } = useSidebar();
const pathname = usePathname();
const isLibraryPage = pathname.startsWith("/library");
const config = getSidebarConfig(pathname);
const hasSidebar = config && config.links.length > 0;
return (
<>
{/* Header spans full width above the grid */}
<Header />
{/* Mobile Library Navigation Bar - Only on library pages */}
{isLibraryPage && <LibraryMobileNav />}
{/* Mobile-First Layout: Single column on mobile, grid on desktop */}
<div className={hasSidebar ? "flex flex-col lg:grid lg:grid-cols-[260px_1fr]" : ""}>
{/* Sidebar - Hidden on mobile, visible on desktop */}
{hasSidebar && (
isLibraryPage ? (
<LibrarySidebarNav />
) : (
<Sidebar open={sidebarOpen} onOpenChange={setSidebarOpen} />
)
)}
{/* Main Content - Full width with mobile padding */}
<main className="w-full px-4 lg:px-0">{children}</main>
</div>
</>
);
}
export function LayoutWrapper({ children }: { children: React.ReactNode }) {
return (
<SidebarProvider>
<LayoutContent>{children}</LayoutContent>
</SidebarProvider>
);
}
// components/library-mobile-nav.tsx
"use client";
import { useState } from "react";
import * as Dialog from "@radix-ui/react-dialog";
import { BookOpen, X } from "lucide-react";
import { LibrarySidebarContent } from "@/components/library-sidebar-content";
export function LibraryMobileNav() {
const [open, setOpen] = useState(false);
return (
<>
{/* Mobile Navigation Bar - High-end technical document header */}
<div className="sticky top-16 z-40 lg:hidden w-full border-b border-zinc-200 bg-zinc-100/50 backdrop-blur">
<button
onClick={() => setOpen(true)}
className="w-full flex items-center gap-2 px-4 py-2.5 text-sm font-medium text-zinc-600 uppercase tracking-widest hover:bg-zinc-200/50 transition-colors"
>
<BookOpen className="h-4 w-4" />
<span>Browse Chapters</span>
</button>
</div>
{/* Mobile Drawer/Sheet */}
<Dialog.Root open={open} onOpenChange={setOpen}>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 z-50 bg-black/50" />
<Dialog.Content className="fixed inset-y-0 left-0 z-50 w-80 border-r border-zinc-200 bg-white p-6">
<LibrarySidebarContent onLinkClick={() => setOpen(false)} />
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
</>
);
}The AI Context
The CSS Grid Shell pattern uses `lg:grid lg:grid-cols-[260px_1fr]` to create two columns. The sidebar uses `lg:sticky lg:top-0 lg:h-screen` (NOT `lg:fixed`). The main content uses `w-full` in the second grid column. Never use `lg:fixed`, `lg:inset-y-0`, or `lg:pl-64` padding hacks. The grid handles all spacing automatically.
On mobile, the layout uses `flex flex-col` (single column). The sidebar is hidden with `hidden lg:block`. For library pages, the `LibraryMobileNav` component provides a sticky "Browse Chapters" bar below the header that opens a drawer for chapter navigation. This decouples global navigation (header) from contextual navigation (sidebar), preventing navigation overload.
**The Law of the Constant Header**: The Global Header must be placed in the root layout (`app/layout.tsx`) with `z-50` and `sticky top-0`. It must never be unmounted or hidden. Contextual navigation (like `LibraryMobileNav`) uses `z-40` and sits below the header. This prevents "User Trapping" by ensuring users always have access to global navigation.Directive for AI Agents
Copy and paste this directive to instruct an AI assistant on how to maintain this specific pattern:
## Directive for AI Agents
When modifying the layout, do not use absolute/fixed positioning for main containers. Refer to the grid-cols mapping in the root layout to maintain the 260px sidebar track.
**Required Pattern:**
- Container: `flex flex-col lg:grid lg:grid-cols-[260px_1fr]` (mobile-first)
- Sidebar: `hidden lg:block lg:sticky lg:top-0 lg:h-screen` (first grid track)
- Main: `w-full px-4 lg:px-0` (second grid track, 1fr)
- Mobile Nav: `LibraryMobileNav` component for library pages (sticky below header)
**Mobile Navigation:**
- Global Navigation: Header contains site-wide links (Home, Library, Search)
- Contextual Navigation: `LibraryMobileNav` provides "Browse Chapters" trigger for library pages
- Never combine global and contextual navigation in the same menu on mobile
- The mobile sidebar drawer should only contain contextual navigation (chapters, volumes)
**The Law of the Constant Header:**
- Global Header MUST be in root layout (`app/layout.tsx`), never in page-specific layouts
- Global Header MUST use `z-50` (highest z-index)
- Global Header MUST use `sticky top-0` to remain visible during scroll
- Contextual navigation (LibraryMobileNav) MUST use `z-40` (below global header)
- Global Header MUST never be conditionally unmounted or hidden
**Forbidden Patterns:**
- ❌ `lg:fixed` positioning
- ❌ `lg:absolute` positioning
- ❌ `lg:pl-64` or padding hacks
- ❌ `lg:inset-y-0` positioning
- ❌ Mixing global and contextual navigation in mobile menus
- ❌ Conditionally hiding the Global Header based on route
- ❌ Placing Global Header in page-specific layouts
- ❌ Using z-index values that allow contextual nav to cover the header
The grid track system ensures zero overlap and automatic spacing. The mobile navigation hierarchy prevents user confusion by clearly separating global and contextual navigation. The Law of the Constant Header prevents "User Trapping" by ensuring the global navigation is always accessible.