component
Metric Card
Stat card with icon, large value, label, and trend indicator (up/down %) for dashboards.
Installation
npx shadcn@latest add https://optimotive-ui.dev.optimotive-tools.co.uk/registry.json metric-cardPreview
Source
components/metric-card.tsx
"use client"
import * as React from "react"
import { TrendingUp, TrendingDown, Minus } from "lucide-react"
import { cn } from "@/lib/utils"
export interface MetricCardProps extends React.HTMLAttributes<HTMLDivElement> {
label: string
value: string | number
trend?: number
icon?: React.ReactNode
description?: string
}
export function MetricCard({ label, value, trend, icon, description, className, ...props }: MetricCardProps) {
const isUp = trend !== undefined && trend > 0
const isDown = trend !== undefined && trend < 0
const trendColor = isUp ? "text-emerald-400" : isDown ? "text-red-400" : "text-slate-400"
const TrendIcon = isUp ? TrendingUp : isDown ? TrendingDown : Minus
return (
<div
className={cn(
"rounded-xl border border-slate-800 bg-slate-900/60 p-6 transition-colors hover:border-slate-700",
className
)}
{...props}
>
<div className="flex items-start justify-between mb-3">
<p className="text-sm font-medium text-slate-400">{label}</p>
{icon && <div className="text-slate-500">{icon}</div>}
</div>
<p className="text-3xl font-bold text-white tracking-tight mb-2">{value}</p>
{trend !== undefined && (
<div className={cn("flex items-center gap-1 text-sm font-medium", trendColor)}>
<TrendIcon className="w-3.5 h-3.5" />
<span>{Math.abs(trend)}%</span>
{description && <span className="text-slate-500 font-normal ml-1">{description}</span>}
</div>
)}
{trend === undefined && description && (
<p className="text-sm text-slate-500">{description}</p>
)}
</div>
)
}