--- name: dashboard-patterns description: Dashboard UI patterns with widget composition, real-time data updates, responsive grid layouts, and data tables for React applications. Use when building dashboards, widgets, or data tables. tags: [dashboard, widgets, data-grid, real-time, layout, admin, tanstack-table, sse] context: fork agent: frontend-ui-developer version: 1.0.0 author: OrchestKit user-invocable: false --- # Dashboard Patterns Dashboard UI patterns for building admin panels, analytics dashboards, and data-driven interfaces with React. ## Layout Patterns ### Responsive Dashboard Grid ```tsx function DashboardLayout({ children }: { children: React.ReactNode }) { return (
{children}
); } function DashboardGrid() { return (
); } ``` ## Widget Components ### Stat Card Widget ```tsx import { TrendingUp, TrendingDown } from 'lucide-react'; interface StatCardProps { title: string; value: string | number; change?: string; changeType?: 'positive' | 'negative' | 'neutral'; icon?: React.ReactNode; } function StatCard({ title, value, change, changeType = 'neutral', icon }: StatCardProps) { return (

{title}

{icon &&
{icon}
}

{value}

{change && ( {changeType === 'positive' && } {changeType === 'negative' && } {change} )}
); } ``` ### Widget Registry Pattern ```tsx type WidgetType = 'stat' | 'chart' | 'table' | 'list'; interface WidgetConfig { id: string; type: WidgetType; title: string; span?: number; props: Record; } const widgetRegistry: Record> = { stat: StatCard, chart: ChartCard, table: DataTable, list: ListWidget, }; function DashboardWidget({ config }: { config: WidgetConfig }) { const Component = widgetRegistry[config.type]; if (!Component) return null; return (
); } ``` ## Real-Time Data Patterns ### TanStack Query + SSE ```tsx import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useEffect } from 'react'; function useRealtimeMetrics() { const queryClient = useQueryClient(); const { data, isLoading } = useQuery({ queryKey: ['metrics'], queryFn: fetchMetrics, }); useEffect(() => { const eventSource = new EventSource('/api/metrics/stream'); eventSource.onmessage = (event) => { const update = JSON.parse(event.data); queryClient.setQueryData(['metrics'], (old: Metrics | undefined) => ({ ...old, ...update, })); }; eventSource.onerror = () => { eventSource.close(); queryClient.invalidateQueries({ queryKey: ['metrics'] }); }; return () => eventSource.close(); }, [queryClient]); return { data, isLoading }; } ``` ## Data Table (TanStack Table) ```tsx import { useReactTable, getCoreRowModel, getSortedRowModel, getFilteredRowModel, getPaginationRowModel, flexRender, type ColumnDef, type SortingState, } from '@tanstack/react-table'; const columns: ColumnDef[] = [ { accessorKey: 'id', header: 'Order ID' }, { accessorKey: 'customer', header: 'Customer' }, { accessorKey: 'amount', header: 'Amount', cell: ({ getValue }) => `$${getValue().toLocaleString()}` }, { accessorKey: 'status', header: 'Status', cell: ({ getValue }) => }, ]; function OrdersTable({ data }: { data: Order[] }) { const [sorting, setSorting] = useState([]); const [globalFilter, setGlobalFilter] = useState(''); const table = useReactTable({ data, columns, state: { sorting, globalFilter }, onSortingChange: setSorting, onGlobalFilterChange: setGlobalFilter, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getFilteredRowModel: getFilteredRowModel(), getPaginationRowModel: getPaginationRowModel(), }); return (
setGlobalFilter(e.target.value)} placeholder="Search orders..." className="mb-4 rounded border px-3 py-2" /> {table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => ( ))} ))} {table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => ( ))} ))}
{flexRender(header.column.columnDef.header, header.getContext())} {header.column.getIsSorted() === 'asc' && ' ↑'} {header.column.getIsSorted() === 'desc' && ' ↓'}
{flexRender(cell.column.columnDef.cell, cell.getContext())}
Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()}
); } ``` ## Skeleton Loading ```tsx function DashboardSkeleton() { return (
{[...Array(4)].map((_, i) => (
))}
{[...Array(5)].map((_, i) => (
))}
); } ``` ## Anti-Patterns (FORBIDDEN) ```tsx // NEVER: Fetch data in every widget independently (duplicated queries) // NEVER: Re-render entire dashboard on single metric change // NEVER: Hardcoded dashboard layout (not responsive) // NEVER: Polling without intervals (infinite loop) // NEVER: Missing loading states (flash of empty state) // NEVER: Real-time updates without debounce (100 re-renders/sec) ``` ## Key Decisions | Decision | Recommendation | |----------|----------------| | Layout | **CSS Grid** for 2D dashboard layouts | | Real-time | **SSE** for server->client, **WebSocket** for bidirectional | | Data table | **TanStack Table** for features | | State | **TanStack Query** with granular keys | | Loading | **Skeleton** for content areas | ## Related Skills - `recharts-patterns` - Chart components for dashboards - `tanstack-query-advanced` - Data fetching patterns - `streaming-api-patterns` - SSE and WebSocket implementation