Stat row
A row of stats in one frame, divided by shared hairlines.
npx shadcn@latest add https://ui.goodtabs.co/r/stat-row.json
With the @goodtabs namespace in components.json: npx shadcn@latest add @goodtabs/stat-row
- Components
- 110
- Installs
- 4.2k
Two stats sharing one hairline.
A row of four
- Visitors
- 12,480
- +6% vs last month
- Signups
- 842
- +11% vs last month
- Median load
- 1.1s
- -0.2s vs last month
- Error rate
- 0.04%
- No change
Cells share edges, with a mono note under each value.
Usage
import { Stat, StatRow } from "@/components/ui/stat-row"
<StatRow>
<Stat label="Visitors" value="12,480" note="+6% vs last month" />
<Stat label="Signups" value="842" />
</StatRow>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| label | string | On Stat. What the number measures. | |
| value | ReactNode | On Stat. The number, already formatted. | |
| note | string | On Stat. A short mono line under the value. |
Source
stat-row/stat-row.tsx
import * as React from "react";
import { cn } from "@/lib/utils";
type StatRowProps = React.ComponentProps<"dl">;
/**
* A row of stats in one frame, split by shared hairlines. Wraps to two columns on phones.
* Each cell draws its right and bottom edge; the inner grid hangs 1px over the frame so the outer edges hide.
*/
export function StatRow({ className, children, ...props }: StatRowProps) {
return (
<div data-slot="stat-row" className={cn("w-full overflow-hidden rounded-lg border bg-background", className)}>
<dl className="m-0 -mr-px -mb-px grid grid-cols-2 sm:auto-cols-fr sm:grid-flow-col" {...props}>
{children}
</dl>
</div>
);
}
type StatProps = Omit<React.ComponentProps<"div">, "children"> & {
/** What the number measures. */
label: string;
/** The number, already formatted. */
value: React.ReactNode;
/** Small mono note under the value, for example a change. */
note?: string;
};
export function Stat({ label, value, note, className, ...props }: StatProps) {
return (
<div data-slot="stat" className={cn("flex flex-col gap-1 border-r border-b p-4", className)} {...props}>
<dt className="text-sm text-muted-foreground">{label}</dt>
<dd className="m-0 text-2xl font-medium tracking-tight [overflow-wrap:anywhere] tabular-nums">{value}</dd>
{note && <dd className="m-0 font-mono text-xs text-muted-foreground">{note}</dd>}
</div>
);
}
For AI agents
Hand your agent this page, the markdown at /components/stat-row/prompt.md, or the Copy AI prompt button above.
When to use
- A strip of headline numbers under a hero or at the top of a report
- When separate cards would add too much chrome
When not to use
- A single number that needs an icon and a footnote: use stat-card
- More than six numbers, where a table scans faster
Agent notes
- Server component. It renders a description list, so label and value stay paired for screen readers
- Columns fit the width, at least 9rem each, and two columns on phones
- An odd number of cells on phones leaves a blank cell with no border; prefer even counts