Skip to content
All Taste components

Data display

Stat card

A headline number with its change against last period and a small trend line.

up · down · good-when-down · sparkline

Revenue, October₹4,82,30012.4%vs September
Outstanding₹1,26,0008.1%lower is better
Overdue invoices72since last week
import { ArrowDownRight, ArrowUpRight } from "lucide-react";
import { cn } from "@/lib/utils";

/** values → SVG paths in a 120×36 box. */
function sparkPaths(values: number[]) {
  const max = Math.max(...values), min = Math.min(...values);
  const line = values
    .map((v, i) => (i ? "L" : "M") + ((i / (values.length - 1)) * 120).toFixed(1) + "," + (32 - ((v - min) / (max - min || 1)) * 28).toFixed(1))
    .join(" ");
  return { line, area: line + " L120,36 L0,36 Z" };
}

export function StatCard({ label, value, change, direction, good, caption, trend }: {
  label: string;
  value: string;
  change: string;
  direction: "up" | "down";
  /** Whether this change is good news — colours the chip, not the arrow. */
  good: boolean;
  caption: string;
  trend: number[];
}) {
  const { line, area } = sparkPaths(trend);
  const Arrow = direction === "up" ? ArrowUpRight : ArrowDownRight;
  return (
    <div className="grid min-w-0 gap-1 rounded-lg border border-border bg-bg p-4">
      <p className="text-[13px] font-medium text-fg-muted">{label}</p>
      <p className="text-2xl font-semibold tabular-nums tracking-tight">{value}</p>
      <p className="flex items-center gap-1.5 text-[12.5px] text-fg-subtle">
        <span className={cn(
          "inline-flex h-5 items-center gap-0.5 rounded-full px-1.5 text-xs font-medium tabular-nums",
          good ? "bg-success-soft text-success-fg" : "bg-danger-soft text-danger-fg",
        )}>
          <Arrow className="size-3" aria-hidden />{change}
        </span>
        {caption}
      </p>
      <svg viewBox="0 0 120 36" preserveAspectRatio="none" className="mt-2 h-9 w-full overflow-visible" role="img" aria-label={label + " trend"}>
        <path d={area} className="fill-primary/10" />
        <path d={line} fill="none" className="stroke-primary" strokeWidth={1.75} strokeLinecap="round" strokeLinejoin="round" vectorEffect="non-scaling-stroke" />
      </svg>
    </div>
  );
}

// <div className="grid grid-cols-[repeat(auto-fit,minmax(180px,1fr))] gap-3">
//   <StatCard label="Revenue, October" value="₹4,82,300" change="12.4%" direction="up" good caption="vs September" trend={[22, 24, 23, 28, 26, 31, 30, 35, 34, 39, 41]} />
//   <StatCard label="Outstanding" value="₹1,26,000" change="8.1%" direction="down" good caption="lower is better" trend={[40, 38, 39, 34, 35, 31, 32, 27, 28, 24, 22]} />
// </div>

The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.