Layout Patterns

Build responsive page layouts with sidebars, grids, and containers.

Overview#

Layouts provide the structural foundation for your pages. This pattern covers:

  • App shell with header and sidebar
  • Collapsible sidebars
  • Responsive grid layouts
  • Centered content layouts
  • Split-view layouts

App Shell Layout#

A complete app shell with header and sidebar.

1// components/layout/AppShell.tsx 2import { Header } from './Header' 3import { Sidebar } from './Sidebar' 4 5export function AppShell({ children }: { children: React.ReactNode }) { 6 return ( 7 <div className="flex min-h-screen flex-col"> 8 <Header /> 9 <div className="flex flex-1"> 10 <Sidebar /> 11 <main className="flex-1 p-6">{children}</main> 12 </div> 13 </div> 14 ) 15} 16 17// app/dashboard/layout.tsx 18export default function DashboardLayout({ 19 children 20}: { 21 children: React.ReactNode 22}) { 23 return <AppShell>{children}</AppShell> 24}

Collapsible Sidebar#

A sidebar that can be collapsed to save space.

1// components/layout/CollapsibleSidebar.tsx 2'use client' 3 4import { useState, createContext, useContext } from 'react' 5import { ChevronLeft, ChevronRight } from 'lucide-react' 6 7const SidebarContext = createContext<{ 8 collapsed: boolean 9 setCollapsed: (collapsed: boolean) => void 10}>({ collapsed: false, setCollapsed: () => {} }) 11 12export function SidebarProvider({ children }: { children: React.ReactNode }) { 13 const [collapsed, setCollapsed] = useState(false) 14 15 return ( 16 <SidebarContext.Provider value={{ collapsed, setCollapsed }}> 17 {children} 18 </SidebarContext.Provider> 19 ) 20} 21 22export function useSidebar() { 23 return useContext(SidebarContext) 24} 25 26export function CollapsibleSidebar({ children }: { children: React.ReactNode }) { 27 const { collapsed, setCollapsed } = useSidebar() 28 29 return ( 30 <aside 31 className={`relative border-r bg-gray-50 transition-all duration-300 ${ 32 collapsed ? 'w-16' : 'w-64' 33 }`} 34 > 35 <button 36 onClick={() => setCollapsed(!collapsed)} 37 className="absolute -right-3 top-6 z-10 rounded-full border bg-white p-1 shadow-sm" 38 aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'} 39 > 40 {collapsed ? ( 41 <ChevronRight className="h-4 w-4" /> 42 ) : ( 43 <ChevronLeft className="h-4 w-4" /> 44 )} 45 </button> 46 47 <div className={collapsed ? 'overflow-hidden' : ''}> 48 {children} 49 </div> 50 </aside> 51 ) 52}

Container#

A responsive container for centering content.

1// components/ui/Container.tsx 2import { cn } from '@/lib/utils' 3 4interface Props { 5 children: React.ReactNode 6 size?: 'sm' | 'md' | 'lg' | 'xl' | 'full' 7 className?: string 8} 9 10const sizeClasses = { 11 sm: 'max-w-2xl', 12 md: 'max-w-4xl', 13 lg: 'max-w-6xl', 14 xl: 'max-w-7xl', 15 full: 'max-w-full' 16} 17 18export function Container({ children, size = 'xl', className = '' }: Props) { 19 return ( 20 <div className={cn(`mx-auto px-4`, sizeClasses[size], className)}> 21 {children} 22 </div> 23 ) 24} 25 26// Usage 27<Container size="lg"> 28 <h1>Page Title</h1> 29 <p>Content goes here</p> 30</Container>

Grid Layouts#

Responsive grid system for cards and content.

1// components/layout/Grid.tsx 2import { cn } from '@/lib/utils' 3 4interface Props { 5 children: React.ReactNode 6 cols?: 1 | 2 | 3 | 4 | 6 7 gap?: 'sm' | 'md' | 'lg' 8 className?: string 9} 10 11export function Grid({ children, cols = 3, gap = 'md', className = '' }: Props) { 12 const colClasses = { 13 1: 'grid-cols-1', 14 2: 'grid-cols-1 md:grid-cols-2', 15 3: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3', 16 4: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-4', 17 6: 'grid-cols-2 md:grid-cols-3 lg:grid-cols-6' 18 } 19 20 const gapClasses = { 21 sm: 'gap-2', 22 md: 'gap-4', 23 lg: 'gap-6' 24 } 25 26 return ( 27 <div className={cn(`grid`, colClasses[cols], gapClasses[gap], className)}> 28 {children} 29 </div> 30 ) 31} 32 33// Usage 34<Grid cols={3} gap="lg"> 35 <Card>...</Card> 36 <Card>...</Card> 37 <Card>...</Card> 38</Grid>

Stack Layout#

Flexible stack for vertical or horizontal layouts.

1// components/ui/Stack.tsx 2import { cn } from '@/lib/utils' 3 4interface Props { 5 children: React.ReactNode 6 direction?: 'vertical' | 'horizontal' 7 gap?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' 8 align?: 'start' | 'center' | 'end' | 'stretch' 9 justify?: 'start' | 'center' | 'end' | 'between' | 'around' 10 className?: string 11} 12 13export function Stack({ 14 children, 15 direction = 'vertical', 16 gap = 'md', 17 align = 'stretch', 18 justify = 'start', 19 className = '' 20}: Props) { 21 const gapClasses = { xs: 'gap-1', sm: 'gap-2', md: 'gap-4', lg: 'gap-6', xl: 'gap-8' } 22 const alignClasses = { start: 'items-start', center: 'items-center', end: 'items-end', stretch: 'items-stretch' } 23 const justifyClasses = { start: 'justify-start', center: 'justify-center', end: 'justify-end', between: 'justify-between', around: 'justify-around' } 24 25 return ( 26 <div 27 className={cn( 28 'flex', 29 direction === 'vertical' ? 'flex-col' : 'flex-row', 30 gapClasses[gap], 31 alignClasses[align], 32 justifyClasses[justify], 33 className 34 )} 35 > 36 {children} 37 </div> 38 ) 39} 40 41// Usage 42<Stack direction="horizontal" gap="lg" align="center" justify="between"> 43 <Logo /> 44 <Nav /> 45 <UserMenu /> 46</Stack>

Split Layout#

A two-column layout for master-detail views.

1// components/layout/SplitLayout.tsx 2interface Props { 3 left: React.ReactNode 4 right: React.ReactNode 5 leftWidth?: string 6} 7 8export function SplitLayout({ 9 left, 10 right, 11 leftWidth = 'w-1/3' 12}: Props) { 13 return ( 14 <div className="flex min-h-screen"> 15 <div className={`${leftWidth} border-r bg-gray-50 p-6`}> 16 {left} 17 </div> 18 <div className="flex-1 p-6">{right}</div> 19 </div> 20 ) 21} 22 23// Usage - Email app layout 24<SplitLayout 25 left={<EmailList emails={emails} />} 26 right={<EmailDetail email={selectedEmail} />} 27/>

Responsive Layout#

A layout that adapts between desktop sidebar and mobile header.

1// components/layout/ResponsiveLayout.tsx 2interface Props { 3 sidebar: React.ReactNode 4 main: React.ReactNode 5} 6 7export function ResponsiveLayout({ sidebar, main }: Props) { 8 return ( 9 <div className="lg:flex"> 10 {/* Mobile: Full width, hidden on desktop */} 11 <div className="lg:hidden"> 12 <MobileHeader /> 13 </div> 14 15 {/* Desktop: Fixed sidebar */} 16 <aside className="hidden lg:block lg:w-64 lg:shrink-0"> 17 <div className="sticky top-0 h-screen overflow-y-auto border-r p-4"> 18 {sidebar} 19 </div> 20 </aside> 21 22 {/* Main content */} 23 <main className="flex-1 p-4 lg:p-8"> 24 {main} 25 </main> 26 </div> 27 ) 28}

Centered Layout#

Center content for auth pages and single forms.

1// components/layout/CenteredLayout.tsx 2export function CenteredLayout({ children }: { children: React.ReactNode }) { 3 return ( 4 <div className="flex min-h-screen items-center justify-center bg-gray-50 p-4"> 5 <div className="w-full max-w-md">{children}</div> 6 </div> 7 ) 8} 9 10// Usage - Login page 11export default function LoginPage() { 12 return ( 13 <CenteredLayout> 14 <Card> 15 <CardHeader> 16 <CardTitle>Sign In</CardTitle> 17 </CardHeader> 18 <CardContent> 19 <LoginForm /> 20 </CardContent> 21 </Card> 22 </CenteredLayout> 23 ) 24}

Best Practices#

  1. Use CSS Grid for 2D layouts - Grid is better than flexbox for complex layouts
  2. Make sidebars sticky - Keep navigation visible while scrolling
  3. Consider viewport height - Use min-h-screen for full-height layouts
  4. Handle mobile responsively - Sidebars should become drawers on mobile
  5. Provide consistent spacing - Use a spacing scale throughout
  • Navigation - Navigation component patterns
  • Cards - Card layout patterns
  • Tabs - Tab navigation patterns