--- name: grid-layout-system description: A structured CSS Grid design system for building polished, production-quality app shells and marketing pages. Implements the layout patterns found in Linear, Vercel, and Resend — sidebar rails, content panels, responsive sidebars — using Tailwind CSS v4. Zero layout drift across breakpoints. Apply when building any multi-column app layout, dashboard, or landing page that needs professional structure. --- # Grid Layout System **Technique:** Two Primitives, Infinite Layouts Every complex layout is composed of exactly two primitives: a **stack** (vertical) and a **grid** (two-dimensional). Master those two with CSS Grid and you never need a layout library again. ## The Two Primitives ```css /* Primitive 1: Stack — vertical flow, no columns */ .stack { display: grid; gap: var(--space); } /* Primitive 2: Grid — explicit rows AND columns */ .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(var(--min), 1fr)); gap: var(--space); } ``` Compose these two. Nest them. That's the whole system. ## App Shell — The 3-Zone Layout The standard app shell used by Linear, Vercel dashboard, Resend, and Supabase: ``` ┌──────────┬────────────────────────────┐ │ │ Top bar (fixed height) │ │ Sidebar │────────────────────────────│ │ (rail) │ Content area │ │ │ (scrollable) │ └──────────┴────────────────────────────┘ ``` ```tsx // app/dashboard/layout.tsx export default function DashboardLayout({ children }: { children: React.ReactNode }) { return (
{/* Top bar — spans both columns */}
{/* Sidebar — fixed, scrollable independently */} {/* Content — scrollable independently */}
{children}
); } ``` **Key properties:** - `h-dvh` — full viewport height (dynamic, accounts for mobile browser chrome) - `grid-rows-[56px_1fr]` — fixed header, flexible content - `overflow-hidden` on root — prevents double scrollbars - `overflow-y-auto` only on sidebar and main — each scrolls independently ## Responsive Sidebar Collapse ```tsx // components/Sidebar.tsx "use client"; import { useState } from "react"; export function AppShell({ children }: { children: React.ReactNode }) { const [open, setOpen] = useState(false); return (
{/* Mobile hamburger */}
{/* Sidebar — drawer on mobile, static on desktop */} {/* Overlay for mobile */} {open && (
setOpen(false)} /> )}
{children}
); } ``` ## Content Area Grid Inside the main content area, use a responsive card grid: ```tsx // Responsive 3-column card grid
{items.map((item) => )}
``` For a masonry-like layout that fills available width: ```tsx // Auto-fill: as many columns as fit at minimum 280px
{items.map((item) => )}
``` ## Detail Panel Layout (Split View) Linear-style — list on left, detail panel on right: ```tsx
{/* List — scrollable */}
{items.map((item) => (

{item.title}

{item.subtitle}

))}
{/* Detail — scrollable */}
{selectedItem ? : }
``` ## Structural Grid (Rail Lines) The Linear/Resend aesthetic — grid lines are visible design elements, not hidden scaffolding. ```css /* globals.css */ .page-rails { --rail-offset: max(1rem, calc(50% - 36rem)); position: relative; overflow-x: clip; /* NEVER overflow-x: hidden — breaks position:sticky */ } .page-rails::before, .page-rails::after { content: ""; position: absolute; top: 0; bottom: 0; width: 1px; background: rgb(0 0 0 / 0.08); pointer-events: none; } .page-rails::before { left: var(--rail-offset); } .page-rails::after { right: var(--rail-offset); } html.dark .page-rails::before, html.dark .page-rails::after { background: rgb(255 255 255 / 0.08); } .rail-bounded { margin-left: var(--rail-offset); margin-right: var(--rail-offset); } ``` ```tsx // Marketing page with structural grid


``` ## Feature Grid with Internal Dashed Borders The card grid pattern used across Linear, Vercel, Resend — cells divided by dashed lines, no card backgrounds: ```tsx function FeatureGrid({ features }: { features: Feature[] }) { return (
{features.map((feature, i) => (
= 3 ? "lg:border-t lg:border-dashed lg:border-black/8 dark:lg:border-white/8" : "", // Top border: not in first 2-col row (tablet) i >= 2 ? "sm:max-lg:border-t sm:max-lg:border-dashed sm:max-lg:border-black/8 dark:sm:max-lg:border-white/8" : "", // Top border: all after first (mobile single-col) i >= 1 ? "max-sm:border-t max-sm:border-dashed max-sm:border-black/8 dark:max-sm:border-white/8" : "", ].filter(Boolean).join(" ")} >

{feature.title}

{feature.description}

))}
); } ``` ## Tailwind v4 Grid Tokens Add to `globals.css` for reusable grid sizing: ```css @theme inline { --sidebar-width: 240px; --topbar-height: 56px; --content-max-width: 1100px; --rail-offset: max(1rem, calc(50% - 36rem)); } ``` Use in components: ```tsx
``` ## Dot Pattern Background Subtle texture used in alternating sections: ```css .dot-pattern { background-image: radial-gradient( rgb(0 0 0 / 0.04) 1px, transparent 1px ); background-size: 24px 24px; } html.dark .dot-pattern { background-image: radial-gradient( rgb(255 255 255 / 0.04) 1px, transparent 1px ); } ``` ## Implementation Checklist - [ ] `h-dvh` (not `h-screen`) for full-height layouts — accounts for mobile browser chrome - [ ] `overflow-hidden` on the outermost grid container - [ ] `overflow-y-auto` only on individually-scrolling panes (sidebar, main content) - [ ] `overflow-x: clip` (not `hidden`) on `.page-rails` — `hidden` breaks `position: sticky` - [ ] Sidebar is `absolute` + `translate-x` on mobile, `relative` on `lg:` - [ ] All feature grid cells have `group transition-colors hover:bg-*` for hover feedback - [ ] Dashed internal borders use `border-dashed border-black/8` — never hardcoded hex - [ ] Add `scroll-padding-top: 56px` to `html` when using sticky nav with anchor links ## Design Token Reference | Element | Style | |---|---| | Vertical rail lines | `1px solid rgb(0 0 0 / 0.08)` | | Section dividers | `border-black/8 dark:border-white/8` | | Internal grid dividers | `border-dashed border-black/8` | | Card hover | `hover:bg-black/[0.02] dark:hover:bg-white/[0.02]` | | Section label | `font-mono text-[10px] uppercase tracking-widest` | | Dot pattern cell | `24px × 24px`, `4% opacity` | ## Common Pitfalls 1. **`overflow-x: hidden` on rails container** — breaks `position: sticky` on any child. Always use `overflow-x: clip`. 2. **Applying padding to the grid container instead of cells** — when using `items-stretch` for full-height dividers, padding must go on children, not the grid. 3. **`border-l` on the first cell** — the first cell in any row should never have a left border. Use `i % columns !== 0` logic. 4. **Using `vh` instead of `dvh`** — on mobile Safari, `100vh` includes browser chrome, causing overflow. `100dvh` adjusts dynamically. 5. **Forgetting hover states on grid cells** — inconsistent hover coverage makes some sections feel static. Every interactive cell needs `group transition-colors hover:bg-*`. ## When to Apply - SaaS dashboards with sidebar navigation - Admin panels and data-heavy interfaces - Marketing pages with structured section layouts - Any multi-column layout that needs to collapse correctly on mobile ## When NOT to Apply - Single-column content pages (articles, docs) — use `max-w-prose mx-auto` - Apps that need drag-and-drop reordering (use `flex` instead for easier manipulation) - Layouts with highly irregular column counts — CSS Grid subgrid is complex; use a library