Dashboard Patterns
Dashboard UI patterns for building admin panels, analytics dashboards, and data-driven interfaces with React.
Layout Patterns Responsive Dashboard Grid function DashboardLayout({ children }: { children: React.ReactNode }) { return (
function DashboardGrid() { return (
Widget Components Stat Card Widget 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 &&{value}
{change && ( {changeType === 'positive' &&Widget Registry Pattern type WidgetType = 'stat' | 'chart' | 'table' | 'list';
interface WidgetConfig {
id: string;
type: WidgetType;
title: string;
span?: number;
props: Record
const widgetRegistry: Record
function DashboardWidget({ config }: { config: WidgetConfig }) { const Component = widgetRegistry[config.type]; if (!Component) return null;
return (
Real-Time Data Patterns TanStack Query + SSE 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) import { useReactTable, getCoreRowModel, getSortedRowModel, getFilteredRowModel, getPaginationRowModel, flexRender, type ColumnDef, type SortingState, } from '@tanstack/react-table';
const columns: ColumnDef$${getValue<number>().toLocaleString()} },
{ accessorKey: 'status', header: 'Status', cell: ({ getValue }) =>
function OrdersTable({ data }: { data: Order[] }) {
const [sorting, setSorting] = useState
const table = useReactTable({ data, columns, state: { sorting, globalFilter }, onSortingChange: setSorting, onGlobalFilterChange: setGlobalFilter, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getFilteredRowModel: getFilteredRowModel(), getPaginationRowModel: getPaginationRowModel(), });
return (
| {flexRender(header.column.columnDef.header, header.getContext())} {header.column.getIsSorted() === 'asc' && ' ↑'} {header.column.getIsSorted() === 'desc' && ' ↓'} | ))}
|---|
| {flexRender(cell.column.columnDef.cell, cell.getContext())} | ))}
Skeleton Loading function DashboardSkeleton() { return (
Anti-Patterns (FORBIDDEN) // 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