# Dashboard Layout Strategies ## Table of Contents - [Grid System Fundamentals](#grid-system-fundamentals) - [Fixed Layout Pattern](#fixed-layout-pattern) - [Customizable Grid Layout](#customizable-grid-layout) - [Template-Based Layouts](#template-based-layouts) - [Responsive Breakpoints](#responsive-breakpoints) - [Layout Persistence](#layout-persistence) - [Performance Considerations](#performance-considerations) ## Grid System Fundamentals ### 12-Column Grid System ```css .dashboard-grid { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--dashboard-gap, 1rem); padding: var(--dashboard-padding, 1.5rem); } /* Widget spanning */ .widget-sm { grid-column: span 3; } /* 1/4 width */ .widget-md { grid-column: span 4; } /* 1/3 width */ .widget-lg { grid-column: span 6; } /* 1/2 width */ .widget-xl { grid-column: span 8; } /* 2/3 width */ .widget-full { grid-column: span 12; } /* Full width */ ``` ### CSS Grid Dashboard Layout ```tsx function GridDashboard({ widgets }) { return (
{widgets.map(widget => (
))}
); } ``` ## Fixed Layout Pattern ### Executive Dashboard Layout ```tsx const executiveLayout = { desktop: [ { id: 'revenue', col: 0, row: 0, w: 3, h: 2 }, { id: 'users', col: 3, row: 0, w: 3, h: 2 }, { id: 'conversion', col: 6, row: 0, w: 3, h: 2 }, { id: 'churn', col: 9, row: 0, w: 3, h: 2 }, { id: 'chart', col: 0, row: 2, w: 8, h: 4 }, { id: 'table', col: 8, row: 2, w: 4, h: 4 }, { id: 'map', col: 0, row: 6, w: 6, h: 3 }, { id: 'timeline', col: 6, row: 6, w: 6, h: 3 } ] }; function FixedDashboard() { return (
{executiveLayout.desktop.map(item => (
))}
); } ``` ### Predefined Layout Templates ```typescript const layoutTemplates = { salesDashboard: { name: 'Sales Analytics', widgets: [ { type: 'kpi', position: { x: 0, y: 0, w: 3, h: 2 } }, { type: 'kpi', position: { x: 3, y: 0, w: 3, h: 2 } }, { type: 'revenue-chart', position: { x: 0, y: 2, w: 6, h: 4 } }, { type: 'product-table', position: { x: 6, y: 2, w: 6, h: 4 } } ] }, operationsDashboard: { name: 'Operations Monitoring', widgets: [ { type: 'status-grid', position: { x: 0, y: 0, w: 12, h: 2 } }, { type: 'timeline', position: { x: 0, y: 2, w: 8, h: 3 } }, { type: 'alerts', position: { x: 8, y: 2, w: 4, h: 3 } } ] } }; ``` ## Customizable Grid Layout ### React Grid Layout Implementation ```tsx import GridLayout from 'react-grid-layout'; import 'react-grid-layout/css/styles.css'; import 'react-resizable/css/styles.css'; function CustomizableDashboard() { const [layout, setLayout] = useState([ { i: 'a', x: 0, y: 0, w: 4, h: 2 }, { i: 'b', x: 4, y: 0, w: 4, h: 2 }, { i: 'c', x: 8, y: 0, w: 4, h: 2 }, { i: 'd', x: 0, y: 2, w: 6, h: 4 }, { i: 'e', x: 6, y: 2, w: 6, h: 4 } ]); const onLayoutChange = (newLayout) => { setLayout(newLayout); // Save to localStorage or API localStorage.setItem('dashboardLayout', JSON.stringify(newLayout)); }; return (
); } ``` ### Responsive Grid Layout ```tsx import { Responsive, WidthProvider } from 'react-grid-layout'; const ResponsiveGridLayout = WidthProvider(Responsive); function ResponsiveDashboard() { const [layouts, setLayouts] = useState({ lg: [ { i: 'kpi1', x: 0, y: 0, w: 3, h: 2 }, { i: 'kpi2', x: 3, y: 0, w: 3, h: 2 }, { i: 'kpi3', x: 6, y: 0, w: 3, h: 2 }, { i: 'kpi4', x: 9, y: 0, w: 3, h: 2 } ], md: [ { i: 'kpi1', x: 0, y: 0, w: 5, h: 2 }, { i: 'kpi2', x: 5, y: 0, w: 5, h: 2 }, { i: 'kpi3', x: 0, y: 2, w: 5, h: 2 }, { i: 'kpi4', x: 5, y: 2, w: 5, h: 2 } ], sm: [ { i: 'kpi1', x: 0, y: 0, w: 6, h: 2 }, { i: 'kpi2', x: 0, y: 2, w: 6, h: 2 }, { i: 'kpi3', x: 0, y: 4, w: 6, h: 2 }, { i: 'kpi4', x: 0, y: 6, w: 6, h: 2 } ] }); return ( setLayouts(layouts)} > {/* Widgets */} ); } ``` ## Template-Based Layouts ### Dashboard Template System ```tsx interface DashboardTemplate { id: string; name: string; description: string; category: string; widgets: WidgetConfig[]; layout: LayoutConfig; filters: FilterConfig[]; } const templates: DashboardTemplate[] = [ { id: 'sales-overview', name: 'Sales Overview', description: 'Track sales performance and revenue metrics', category: 'Sales', widgets: [ { type: 'kpi', dataSource: 'revenue', config: { period: 'monthly' } }, { type: 'area-chart', dataSource: 'revenue-trend', config: { timeRange: '12months' } } ], layout: { desktop: { cols: 12, rowHeight: 80 }, tablet: { cols: 8, rowHeight: 70 }, mobile: { cols: 4, rowHeight: 60 } }, filters: [ { type: 'date-range', default: 'last-30-days' }, { type: 'region', options: ['all', 'north', 'south'] } ] } ]; function TemplateSelector({ onSelect }) { return (
{templates.map(template => (
onSelect(template)} >

{template.name}

{template.description}

{template.category}
))}
); } ``` ### Applying Templates ```tsx function DashboardFromTemplate({ templateId }) { const template = templates.find(t => t.id === templateId); const [widgets, setWidgets] = useState([]); useEffect(() => { // Initialize widgets from template const initializedWidgets = template.widgets.map((config, index) => ({ id: `widget-${index}`, ...config, data: null, loading: true })); setWidgets(initializedWidgets); // Load data for each widget initializedWidgets.forEach(widget => { loadWidgetData(widget).then(data => { setWidgets(prev => prev.map(w => w.id === widget.id ? { ...w, data, loading: false } : w )); }); }); }, [template]); return ( ); } ``` ## Responsive Breakpoints ### Breakpoint Strategy ```typescript const breakpoints = { mobile: 0, // 0-767px tablet: 768, // 768-1199px desktop: 1200, // 1200-1919px wide: 1920 // 1920px+ }; const getLayoutForBreakpoint = (width: number): LayoutType => { if (width >= breakpoints.wide) return 'wide'; if (width >= breakpoints.desktop) return 'desktop'; if (width >= breakpoints.tablet) return 'tablet'; return 'mobile'; }; ``` ### Responsive Widget Sizing ```tsx function ResponsiveWidget({ widget }) { const [dimensions, setDimensions] = useState({ width: 'auto', height: 'auto' }); useEffect(() => { const updateDimensions = () => { const viewport = window.innerWidth; if (viewport < 768) { // Mobile: full width, fixed height setDimensions({ width: '100%', height: '200px' }); } else if (viewport < 1200) { // Tablet: half width, medium height setDimensions({ width: '50%', height: '250px' }); } else { // Desktop: flexible based on widget type setDimensions({ width: widget.defaultWidth || '25%', height: widget.defaultHeight || '300px' }); } }; updateDimensions(); window.addEventListener('resize', updateDimensions); return () => window.removeEventListener('resize', updateDimensions); }, [widget]); return (
); } ``` ### Mobile-First Layout ```css /* Mobile First Approach */ .dashboard { display: flex; flex-direction: column; gap: 1rem; padding: 1rem; } .widget { width: 100%; min-height: 200px; } /* Tablet */ @media (min-width: 768px) { .dashboard { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1.5rem; padding: 1.5rem; } } /* Desktop */ @media (min-width: 1200px) { .dashboard { grid-template-columns: repeat(12, 1fr); gap: 2rem; padding: 2rem; } .widget-small { grid-column: span 3; } .widget-medium { grid-column: span 4; } .widget-large { grid-column: span 6; } .widget-full { grid-column: span 12; } } ``` ## Layout Persistence ### Saving User Layouts ```tsx class LayoutPersistence { private storageKey = 'dashboard-layout'; saveLayout(userId: string, layout: Layout) { const key = `${this.storageKey}-${userId}`; // Save to localStorage localStorage.setItem(key, JSON.stringify(layout)); // Also save to backend fetch('/api/dashboard/layout', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userId, layout }) }); } loadLayout(userId: string): Layout | null { const key = `${this.storageKey}-${userId}`; // Try localStorage first const stored = localStorage.getItem(key); if (stored) { return JSON.parse(stored); } // Fallback to backend return this.fetchLayoutFromBackend(userId); } async fetchLayoutFromBackend(userId: string): Promise { try { const response = await fetch(`/api/dashboard/layout/${userId}`); if (response.ok) { return await response.json(); } } catch (error) { console.error('Failed to load layout:', error); } return null; } resetLayout(userId: string) { const key = `${this.storageKey}-${userId}`; localStorage.removeItem(key); fetch(`/api/dashboard/layout/${userId}`, { method: 'DELETE' }); } } ``` ### Layout Versioning ```typescript interface VersionedLayout { version: number; created: Date; modified: Date; layout: Layout; metadata: { browser: string; screenResolution: string; userId: string; }; } function migrateLayout(layout: VersionedLayout): VersionedLayout { let migrated = { ...layout }; // Migrate from v1 to v2 if (layout.version === 1) { migrated.layout = convertV1ToV2(layout.layout); migrated.version = 2; } // Migrate from v2 to v3 if (migrated.version === 2) { migrated.layout = convertV2ToV3(migrated.layout); migrated.version = 3; } return migrated; } ``` ## Performance Considerations ### Virtualized Grid for Large Dashboards ```tsx import { FixedSizeGrid } from 'react-window'; function VirtualizedDashboard({ widgets, columns = 4 }) { const rowCount = Math.ceil(widgets.length / columns); const Cell = ({ columnIndex, rowIndex, style }) => { const index = rowIndex * columns + columnIndex; if (index >= widgets.length) return null; return (
); }; return ( {Cell} ); } ``` ### Layout Optimization Techniques ```tsx // Memoize expensive layout calculations const calculateOptimalLayout = useMemo(() => { return (widgets: Widget[], containerWidth: number) => { // Complex layout algorithm const columns = Math.floor(containerWidth / 300); const layout = []; widgets.forEach((widget, index) => { const row = Math.floor(index / columns); const col = index % columns; layout.push({ id: widget.id, x: col * (containerWidth / columns), y: row * 250, width: containerWidth / columns - 20, height: 230 }); }); return layout; }; }, []); // Debounce resize events const useDebounceResize = (callback: Function, delay = 300) => { useEffect(() => { let timeoutId: NodeJS.Timeout; const handleResize = () => { clearTimeout(timeoutId); timeoutId = setTimeout(callback, delay); }; window.addEventListener('resize', handleResize); return () => { window.removeEventListener('resize', handleResize); clearTimeout(timeoutId); }; }, [callback, delay]); }; ``` ### Progressive Layout Loading ```tsx function ProgressiveDashboard({ widgets }) { const [visibleWidgets, setVisibleWidgets] = useState([]); const [loadingPhase, setLoadingPhase] = useState(0); useEffect(() => { // Phase 1: Load above-the-fold widgets const aboveFold = widgets.slice(0, 4); setVisibleWidgets(aboveFold); setLoadingPhase(1); // Phase 2: Load next batch after delay setTimeout(() => { const nextBatch = widgets.slice(4, 8); setVisibleWidgets(prev => [...prev, ...nextBatch]); setLoadingPhase(2); }, 100); // Phase 3: Load remaining widgets setTimeout(() => { const remaining = widgets.slice(8); setVisibleWidgets(prev => [...prev, ...remaining]); setLoadingPhase(3); }, 300); }, [widgets]); return (
{visibleWidgets.map(widget => ( ))} {loadingPhase < 3 && (
Loading more widgets...
)}
); } ```