- Gallery
- Dashboard & Analytics
- StatsComparison
New
StatsComparison
Side-by-side metric comparison with visual progress bars and delta indicators
Dashboard & Analyticscomparisonstatsmetricsdelta
Dependencies
shadcn/ui components needed:
npx shadcn@latest add cardnpx shadcn@latest add badgenpx shadcn@latest add separatorHow to use this component
Copy the code below into your project. Make sure you have the required shadcn/ui dependencies installed. Then import and use the component in your pages or layouts.
Code
1"use client"23import * as React from "react"4import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"5import { Badge } from "@/components/ui/badge"6import { Separator } from "@/components/ui/separator"7import { cn } from "@/lib/utils"89interface Metric {10 name: string11 thisPeriod: number12 lastPeriod: number13}1415interface StatsComparisonProps {16 metrics?: Metric[]17 className?: string18}1920export function StatsComparison({ metrics = [21 { name: "Revenue", thisPeriod: 84500, lastPeriod: 72000 },22 { name: "Users", thisPeriod: 12400, lastPeriod: 10800 },23 { name: "Orders", thisPeriod: 3200, lastPeriod: 3500 },24 { name: "Conversion", thisPeriod: 3.2, lastPeriod: 2.8 },25], className }: StatsComparisonProps) {26 const formatValue = (val: number) => val >= 1000 ? `${(val / 1000).toFixed(1)}k` : val.toFixed(1)2728 return (29 <Card className={cn("w-full", className)}>30 <CardHeader>31 <CardTitle>Period Comparison</CardTitle>32 </CardHeader>33 <CardContent>34 <div className="space-y-4">35 {metrics.map((metric, i) => {36 const delta = ((metric.thisPeriod - metric.lastPeriod) / metric.lastPeriod) * 10037 const isPositive = delta >= 038 const maxValue = Math.max(metric.thisPeriod, metric.lastPeriod)39 40 return (41 <div key={i}>42 <div className="flex items-center justify-between mb-2">43 <span className="font-medium text-sm">{metric.name}</span>44 <Badge variant={isPositive ? "default" : "destructive"} className={cn(45 "text-xs",46 isPositive ? "bg-emerald-500/10 text-emerald-600 hover:bg-emerald-500/20" : "bg-red-500/10 text-red-600 hover:bg-red-500/20"47 )}>48 {isPositive ? "+" : ""}{delta.toFixed(1)}%49 </Badge>50 </div>51 <div className="grid grid-cols-2 gap-4 mb-2">52 <div>53 <p className="text-xs text-muted-foreground">This Period</p>54 <p className="text-lg font-semibold">{formatValue(metric.thisPeriod)}</p>55 </div>56 <div className="text-right">57 <p className="text-xs text-muted-foreground">Last Period</p>58 <p className="text-lg font-semibold text-muted-foreground">{formatValue(metric.lastPeriod)}</p>59 </div>60 </div>61 <div className="flex gap-1 h-2 rounded-full overflow-hidden bg-muted">62 <div63 className={cn("rounded-l-full transition-all duration-500", isPositive ? "bg-blue-500" : "bg-blue-400")}64 style={{ width: `${(metric.thisPeriod / maxValue) * 50}%` }}65 />66 <div67 className={cn("rounded-r-full transition-all duration-500", "bg-slate-400")}68 style={{ width: `${(metric.lastPeriod / maxValue) * 50}%` }}69 />70 </div>71 {i < metrics.length - 1 && <Separator className="mt-4" />}72 </div>73 )74 })}75 </div>76 </CardContent>77 </Card>78 )79}