Compact weekly support metrics card with six figures in two columns, each with a change arrow and comparison text, kept level by a subgrid. Worsening changes show in red.
import { ArrowDownIcon, ArrowUpIcon } from "@phosphor-icons/react/dist/ssr"
import { cn } from "cn"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
// The copy: change text here, not in the markup.
const COPY = {
title: "Support this week",
description: "Oct 19–25, against Oct 12–18",
against: "than last week",
} as const
type Metric = {
label: string
value: string
note?: string
change: { direction: "up" | "down"; text: string; worse?: boolean }
}
// The week opened with 57 tickets: 57 + 132 new − 141 solved leaves 48.
// Satisfaction is 49 good ratings out of 52.
const METRICS: Metric[] = [
{
label: "Open",
value: "48",
change: { direction: "down", text: "9 fewer" },
},
{ label: "New", value: "132", change: { direction: "up", text: "14 more" } },
{
label: "Solved",
value: "141",
change: { direction: "up", text: "22 more" },
},
{
label: "First reply",
value: "1h 12m",
note: "median",
change: { direction: "down", text: "8m faster" },
},
{
label: "Resolution",
value: "9h 40m",
note: "median",
change: { direction: "up", text: "1h 5m slower", worse: true },
},
{
label: "Satisfaction",
value: "94%",
note: "of 52 ratings",
change: { direction: "up", text: "2 pts higher" },
},
]
export default function StatsCard01() {
return (
<Card className="mx-auto w-full max-w-sm">
<CardHeader>
<CardTitle>
<h2>{COPY.title}</h2>
</CardTitle>
<CardDescription>{COPY.description}</CardDescription>
</CardHeader>
<CardContent>
{/* Volume runs down the first column, speed and satisfaction down the
second. Each cell spans three of the nine rows (label, value,
change), so the lines stay level across a row even when one wraps;
add a row of cells and the grid needs three more rows. */}
<dl className="grid grid-flow-col grid-cols-2 grid-rows-[repeat(9,auto)] gap-x-4 gap-y-6">
{METRICS.map((metric) => {
const Arrow =
metric.change.direction === "up" ? ArrowUpIcon : ArrowDownIcon
return (
<div
key={metric.label}
className="row-span-3 grid min-w-0 grid-rows-subgrid gap-y-1"
>
<dt className="text-xs text-muted-foreground">
{metric.label}
</dt>
<dd className="flex flex-wrap items-baseline gap-x-1">
<span className="text-xl font-medium tabular-nums">
{metric.value}
</span>
{metric.note ? (
<span className="text-xs text-muted-foreground">
{metric.note}
</span>
) : null}
</dd>
<dd
className={cn(
"flex items-center gap-1 text-xs",
metric.change.worse
? "text-destructive"
: "text-muted-foreground"
)}
>
<Arrow aria-hidden="true" className="size-3 shrink-0" />
<span>
{metric.change.text}
<span className="sr-only"> {COPY.against}</span>
</span>
</dd>
</div>
)
})}
</dl>
</CardContent>
</Card>
)
}
pnpm dlx shadcn add @01orbital/stats-card-01npx shadcn add @01orbital/stats-card-01yarn shadcn add @01orbital/stats-card-01bunx --bun shadcn add @01orbital/stats-card-01Free under the MIT license. Add the @01orbital registry to components.json once (install guide), then run the command.
It lands at components/blocks/stats/stats-card-01/index.tsx, as source you own and edit.
import StatsCard01 from "@/components/blocks/stats/stats-card-01"
export default function Example() {
return <StatsCard01 />
}npm: @phosphor-icons/react, cn. The CLI installs them for you.
Registry pieces it installs with it:
cardTwo-Panel Marketplace Stats
Dark marketplace layout of two raised panels with artwork and display-face headlines, a row of three stat cards between them, and an artist pitch with a sell button.
Yearly Goals Progress Card
Yearly goals card with a progress bar per goal, an expected-by-today pace marker, owner names, and met, on-pace or behind-pace status lines.