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#
- Use CSS Grid for 2D layouts - Grid is better than flexbox for complex layouts
- Make sidebars sticky - Keep navigation visible while scrolling
- Consider viewport height - Use min-h-screen for full-height layouts
- Handle mobile responsively - Sidebars should become drawers on mobile
- Provide consistent spacing - Use a spacing scale throughout
Related Patterns#
- Navigation - Navigation component patterns
- Cards - Card layout patterns
- Tabs - Tab navigation patterns