# KPI Card Design Patterns ## Table of Contents - [Essential Elements](#essential-elements) - [Number Formatting](#number-formatting) - [Trend Indicators](#trend-indicators) - [Sparkline Integration](#sparkline-integration) - [Card Variations](#card-variations) - [Interactive Features](#interactive-features) - [Accessibility](#accessibility) ## Essential Elements ### Basic KPI Card Structure ```tsx interface KPICard { label: string; // "Monthly Revenue" value: number; // 1245832 unit?: string; // "$", "%", "users" trend?: { direction: 'up' | 'down' | 'neutral'; value: number; // 15.3 comparison: string; // "vs last month" }; sparkline?: number[]; // [100, 120, 115, 140, 155] status?: 'success' | 'warning' | 'error' | 'neutral'; icon?: ReactNode; onClick?: () => void; } ``` ### Visual Hierarchy ``` ┌──────────────────────────────┐ │ 👥 Active Users [ℹ️] [⚙️] │ ← Icon, Label, Actions │ │ │ 12,543 │ ← Primary Value (largest) │ │ │ ↑ 234 (+1.9%) │ ← Change (medium) │ vs yesterday │ ← Comparison (small) │ │ │ ▂▃▅▄▆▇█▆▅ Last 7 days │ ← Sparkline + Period └──────────────────────────────┘ ``` ## Number Formatting ### Large Number Abbreviation ```typescript function formatLargeNumber(value: number): string { const abbreviations = [ { threshold: 1e9, suffix: 'B' }, { threshold: 1e6, suffix: 'M' }, { threshold: 1e3, suffix: 'K' } ]; for (const { threshold, suffix } of abbreviations) { if (Math.abs(value) >= threshold) { const formatted = (value / threshold).toFixed(1); return `${formatted}${suffix}`; } } return value.toLocaleString(); } // Examples: // 1234567 → "1.2M" // 45678 → "45.7K" // 999 → "999" ``` ### Currency Formatting ```typescript function formatCurrency(value: number, currency = 'USD'): string { const formatter = new Intl.NumberFormat('en-US', { style: 'currency', currency, minimumFractionDigits: 0, maximumFractionDigits: 0, notation: value > 1e6 ? 'compact' : 'standard' }); return formatter.format(value); } // Examples: // 1234567 → "$1.2M" // 45678 → "$45,678" // 999.99 → "$1,000" ``` ### Percentage Formatting ```typescript function formatPercentage(value: number, decimals = 1): string { const formatted = value.toFixed(decimals); const sign = value > 0 ? '+' : ''; return `${sign}${formatted}%`; } // Examples: // 15.345 → "+15.3%" // -2.1 → "-2.1%" // 0 → "0.0%" ``` ## Trend Indicators ### Direction Arrows ```tsx function TrendArrow({ direction, value }: TrendProps) { const arrows = { up: '↑', down: '↓', neutral: '→' }; const colors = { up: 'text-green-600', down: 'text-red-600', neutral: 'text-gray-500' }; return ( {arrows[direction]} {formatPercentage(value)} ); } ``` ### Status Badges ```tsx function StatusBadge({ deltaType, value, label }: BadgeProps) { const styles = { increase: 'bg-green-100 text-green-800', decrease: 'bg-red-100 text-red-800', neutral: 'bg-gray-100 text-gray-800' }; return (
{value} {label && {label}}
); } ``` ## Sparkline Integration ### Basic Sparkline ```tsx import { Sparklines, SparklinesLine, SparklinesSpots } from 'react-sparklines'; function KPISparkline({ data, color = '#10b981' }) { return ( ); } ``` ### Inline SVG Sparkline (No Library) ```tsx function MiniSparkline({ data, width = 100, height = 30 }) { const max = Math.max(...data); const min = Math.min(...data); const range = max - min || 1; const points = data.map((value, index) => { const x = (index / (data.length - 1)) * width; const y = height - ((value - min) / range) * height; return `${x},${y}`; }).join(' '); return ( ); } ``` ## Card Variations ### Compact KPI Card ```tsx function CompactKPI({ label, value, trend }) { return (

{label}

{value}

{trend && (
)}
); } ``` ### Detailed KPI Card ```tsx function DetailedKPI({ label, value, trend, sparkline, breakdown, actions }) { return ( {/* Header */}

{label}

{actions?.map(action => ( ))}
{/* Main Value */}

{value}

{trend && }
{/* Sparkline */} {sparkline && (
)} {/* Breakdown */} {breakdown && (
{breakdown.map(item => (
{item.label} {item.value}
))}
)}
); } ``` ### Goal-Based KPI Card ```tsx function GoalKPI({ label, current, target, unit = '' }) { const percentage = (current / target) * 100; const status = percentage >= 100 ? 'success' : percentage >= 75 ? 'warning' : 'error'; return (

{label}

{unit}{current.toLocaleString()} of {unit}{target.toLocaleString()}

{percentage.toFixed(1)}% of goal achieved

); } ``` ## Interactive Features ### Clickable KPI with Drill-Down ```tsx function InteractiveKPI({ data, onDrillDown }) { const [isHovered, setIsHovered] = useState(false); return ( setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} onClick={() => onDrillDown(data.id)} style={{ boxShadow: isHovered ? 'var(--shadow-lg)' : 'var(--shadow-md)' }} >

{data.label}

{data.value}

{isHovered && ( )}
{data.trend && (
)}
); } ``` ### Real-Time Updating KPI ```tsx function LiveKPI({ endpoint, label }) { const [data, setData] = useState(null); const [isUpdating, setIsUpdating] = useState(false); useEffect(() => { const eventSource = new EventSource(endpoint); eventSource.onmessage = (event) => { setIsUpdating(true); const newData = JSON.parse(event.data); setTimeout(() => { setData(newData); setIsUpdating(false); }, 300); }; return () => eventSource.close(); }, [endpoint]); return (

{label}

{data?.value || '---'}

{data?.lastUpdated && (

Updated {formatRelativeTime(data.lastUpdated)}

)}
); } ``` ## Accessibility ### ARIA Labels and Roles ```tsx function AccessibleKPI({ label, value, trend }) { const trendDescription = trend ? `${trend.direction} ${trend.value}% ${trend.comparison}` : ''; return (

{label}

Current value: {value}
{trend && (
Trend:
)}
); } ``` ### Keyboard Navigation ```tsx function KeyboardNavigableKPI({ data, onSelect }) { return (
{ if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onSelect(data); } }} onClick={() => onSelect(data)} className="kpi-card focus:ring-2 focus:ring-blue-500" > {/* KPI content */}
); } ``` ## Complete KPI Card Implementation ```tsx function KPICard({ label, value, unit = '', trend, sparkline, status = 'neutral', icon, onClick, className = '' }) { const formattedValue = typeof value === 'number' ? formatLargeNumber(value) : value; const statusColors = { success: 'border-l-4 border-green-500', warning: 'border-l-4 border-yellow-500', error: 'border-l-4 border-red-500', neutral: '' }; return ( {/* Header */}
{icon && {icon}}

{label}

{/* Value */}

{unit && {unit}} {formattedValue}

{/* Trend */} {trend && (
{formatPercentage(trend.value)} {trend.comparison}
)} {/* Sparkline */} {sparkline && sparkline.length > 0 && (
)}
); } ```