# Weekly Support KPI Card

> 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.

A shadcn/ui block from the [@01orbital](https://01orbital.dev/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/01orbital/stats-card-01).

- Page, with a live preview: https://shadcn.exchange/r/01orbital/stats-card-01
- Screenshot: https://shadcn.exchange/shot/01orbital/stats-card-01/light.png
- Registry item: https://01orbital.dev/r/stats-card-01.json
- Categories: stats
- Listed: 2026-11-01, source last changed 2026-11-01

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/01orbital/stats-card-01.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `@phosphor-icons/react`, `cn`
- Registry items: `card`

## Files

### `blocks/stats/stats-card-01/index.tsx`

```tsx
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>
  )
}
```
