Requests
4.8k+12%
Tokens (M)
6.4+5%
Spend by model ($)
$165
FableOpusSonnetHaiku
import { BarChart, Sparkline, StatTile } from "@/components/pandacoderz-ui/mini-chart";
const requests = [120, 180, 160, 240, 220, 310, 290, 350, 330, 420, 410, 480];
const tokens = [3.1, 2.8, 3.4, 3.9, 3.6, 4.4, 4.1, 5.2, 4.9, 5.8, 6.1, 6.4];
const byModel = [
{ label: "Fable", value: 48 },
{ label: "Opus", value: 31 },
{ label: "Sonnet", value: 64 },
{ label: "Haiku", value: 22 },
];
export default function MiniChartDemo() {
return (
<div className="grid w-full max-w-2xl gap-3 sm:grid-cols-3">
<StatTile label="Requests" value="4.8k" delta="+12%" deltaTone="up" chart={<Sparkline data={requests} />} />
<StatTile label="Tokens (M)" value="6.4" delta="+5%" deltaTone="up" chart={<Sparkline data={tokens} className="text-chart-2" />} />
<StatTile label="Spend by model ($)" value="$165" chart={<BarChart data={byModel} height={48} format={(v) => `$${v}`} showValues />} />
</div>
);
}Installation
npx shadcn@latest add https://ui.spencerwueste.com/r/mini-chart.jsonpnpm dlx shadcn@latest add https://ui.spencerwueste.com/r/mini-chart.jsonyarn dlx shadcn@latest add https://ui.spencerwueste.com/r/mini-chart.jsonbunx --bun shadcn@latest add https://ui.spencerwueste.com/r/mini-chart.jsonCopy the source into your project:
"use client";
import * as React from "react";
import { cn } from "@/lib/utils";
/**
* Dependency-free SVG charts for dashboards and stat tiles. They are
* intentionally small; reach for a charting library once you need axes,
* tooltips, or more than one series.
*/
type SparklineProps = React.ComponentProps<"svg"> & {
data: number[];
/** Fill the area under the line. @default true */
area?: boolean;
strokeWidth?: number;
/** Highlight the last point. @default true */
dot?: boolean;
};
function Sparkline({ data, area = true, strokeWidth = 1.5, dot = true, className, ...props }: SparklineProps) {
const w = 100;
const h = 32;
const pad = 2;
const max = Math.max(...data, 1);
const min = Math.min(...data, 0);
const range = max - min || 1;
const pts = data.map((v, i) => {
const x = pad + (i / Math.max(1, data.length - 1)) * (w - pad * 2);
const y = h - pad - ((v - min) / range) * (h - pad * 2);
return [x, y] as const;
});
const line = pts.map(([x, y], i) => `${i === 0 ? "M" : "L"}${x.toFixed(2)},${y.toFixed(2)}`).join(" ");
const last = pts[pts.length - 1];
const id = React.useId().replace(/[^a-zA-Z0-9_-]/g, "");
return (
<svg
data-slot="sparkline"
viewBox={`0 0 ${w} ${h}`}
preserveAspectRatio="none"
className={cn("h-8 w-full overflow-visible text-brand", className)}
aria-hidden="true"
{...props}
>
{area ? (
<>
<defs>
<linearGradient id={`grad-${id}`} x1="0" x2="0" y1="0" y2="1">
<stop offset="0%" stopColor="currentColor" stopOpacity="0.25" />
<stop offset="100%" stopColor="currentColor" stopOpacity="0" />
</linearGradient>
</defs>
<path d={`${line} L${last?.[0] ?? w},${h} L${pts[0]?.[0] ?? 0},${h} Z`} fill={`url(#grad-${id})`} />
</>
) : null}
<path d={line} fill="none" stroke="currentColor" strokeWidth={strokeWidth} vectorEffect="non-scaling-stroke" strokeLinejoin="round" strokeLinecap="round" />
{dot && last ? <circle cx={last[0]} cy={last[1]} r={2} fill="currentColor" vectorEffect="non-scaling-stroke" /> : null}
</svg>
);
}
type BarDatum = { label: string; value: number; color?: string };
type BarChartProps = React.ComponentProps<"div"> & {
data: BarDatum[];
/** Chart height in px. @default 120 */
height?: number;
format?: (value: number) => string;
/** Show the value above each bar. */
showValues?: boolean;
};
function BarChart({ data, height = 120, format = (v) => String(v), showValues = false, className, ...props }: BarChartProps) {
const max = Math.max(...data.map((d) => d.value), 1);
return (
<div data-slot="bar-chart" className={cn("flex w-full flex-col gap-2", className)} {...props}>
<div className="flex w-full items-end gap-1.5" style={{ height }}>
{data.map((d) => (
<div key={d.label} className="group/bar flex h-full flex-1 flex-col items-center justify-end gap-1" title={`${d.label}: ${format(d.value)}`}>
{showValues ? <span className="text-[10px] tabular-nums text-muted-foreground opacity-0 transition-opacity group-hover/bar:opacity-100">{format(d.value)}</span> : null}
<div
className="w-full rounded-t-sm bg-brand transition-[height] duration-500 ease-out group-hover/bar:opacity-80"
style={{ height: `${(d.value / max) * 100}%`, background: d.color }}
/>
</div>
))}
</div>
<div className="flex w-full gap-1.5">
{data.map((d) => (
<span key={d.label} className="flex-1 truncate text-center text-[10px] text-muted-foreground">
{d.label}
</span>
))}
</div>
</div>
);
}
type StatTileProps = React.ComponentProps<"div"> & {
label: React.ReactNode;
value: React.ReactNode;
/** Small trailing text such as "+12% vs last week". */
delta?: React.ReactNode;
deltaTone?: "up" | "down" | "neutral";
/** Optional chart rendered under the value. */
chart?: React.ReactNode;
};
function StatTile({ label, value, delta, deltaTone = "neutral", chart, className, ...props }: StatTileProps) {
return (
<div data-slot="stat-tile" className={cn("flex flex-col gap-2 rounded-2xl border bg-card p-4", className)} {...props}>
<span className="text-xs text-muted-foreground">{label}</span>
<div className="flex items-baseline justify-between gap-2">
<span className="text-2xl font-semibold tracking-tight tabular-nums">{value}</span>
{delta ? (
<span
className={cn(
"text-xs tabular-nums",
deltaTone === "up" && "text-emerald-600 dark:text-emerald-400",
deltaTone === "down" && "text-red-600 dark:text-red-400",
deltaTone === "neutral" && "text-muted-foreground",
)}
>
{delta}
</span>
) : null}
</div>
{chart}
</div>
);
}
export { Sparkline, BarChart, StatTile };
export type { BarDatum };Usage
import { BarChart, Sparkline, StatTile } from "@/components/pandacoderz-ui/mini-chart";<StatTile label="Requests" value="4.8k" delta="+12%" deltaTone="up" chart={<Sparkline data={series} />} />
<BarChart data={[{ label: "Opus", value: 31 }, { label: "Sonnet", value: 64 }]} height={80} />Colors come from currentColor, so className="text-chart-2" recolors a sparkline. These are deliberately small; move to a charting library once you need axes or tooltips.
API Reference
Sparkline
| Prop | Type | Description |
|---|---|---|
data |
number[] |
Series. |
area |
boolean |
Gradient fill under the line. Default true. |
dot |
boolean |
Mark the last point. Default true. |
BarChart
| Prop | Type | Description |
|---|---|---|
data |
{ label, value, color? }[] |
Bars. |
height |
number |
Px. Default 120. |
showValues |
boolean |
Reveal values on hover. |
StatTile
| Prop | Type | Description |
|---|---|---|
label / value |
ReactNode |
Headline pair. |
delta / deltaTone |
ReactNode / "up" | "down" | "neutral" |
Trailing change text. |
chart |
ReactNode |
Rendered under the value. |