Browse the docs
ComponentsNo. 54 of 109
Stat
Key figures as a description list: label, value and a caption with an optional trend arrow.
Installation
pnpm dlx shadcn@latest add @corsair-ui/statNo setup needed: the CLI finds @corsair-ui in the shadcn registry directory. The files land in your project as source: edit them like your own code. More in Installation.
- Also adds
- utils
- npm
clsxlucide-reacttailwind-merge
Examples
With trends and tickers
- Nautical miles
- 12,480
- Increase: +8% on last season
- Prizes taken
- 37
- No change: Same as last season
- Days in port
- 21
- Decrease: −5 days
API
| Prop | Type | Default |
|---|---|---|
trendStatCaption: an arrow and words for screen readers. | "up" | "down" | "flat" | — |
trendLabelThose words. | string | "Increase" / "Decrease" / "No change" |
Accessibility
- StatGroup is a description list, so each label is read with its value.
- The trend is not shown by colour and icon alone.
Compatibility
- Tailwind CSS
- 3.4 and 4, both checked in CI
- React
- 19
- Rendering
- Works in server components
Source
1 file, installed as source you own
stat.tsxView on GitHubimport { MinusIcon, TrendingDownIcon, TrendingUpIcon } from "lucide-react";
import type { ComponentProps } from "react";
import { cn } from "@/registry/default/lib/utils";
type StatTrend = "up" | "down" | "flat";
const TREND_ICONS = { up: TrendingUpIcon, down: TrendingDownIcon, flat: MinusIcon } as const;
const TREND_LABELS: Record<StatTrend, string> = {
up: "Increase",
down: "Decrease",
flat: "No change",
};
/**
* A set of key figures, marked up as a description list so each value is
* read together with its label. Lay it out with grid classes
* (`sm:grid-cols-3`, …).
*
* @example
* <StatGroup className="sm:grid-cols-3">
* <Stat>
* <StatLabel>Revenue</StatLabel>
* <StatValue>$48,200</StatValue>
* <StatCaption trend="up">12% since last month</StatCaption>
* </Stat>
* </StatGroup>
*/
function StatGroup({ className, ...props }: ComponentProps<"dl">) {
return <dl data-slot="stat-group" className={cn("grid gap-6", className)} {...props} />;
}
/** One figure: a StatLabel, a StatValue and optionally a StatCaption. Put it inside a StatGroup. */
function Stat({ className, ...props }: ComponentProps<"div">) {
return <div data-slot="stat" className={cn("flex flex-col gap-1", className)} {...props} />;
}
function StatLabel({ className, ...props }: ComponentProps<"dt">) {
return (
<dt
data-slot="stat-label"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
);
}
function StatValue({ className, ...props }: ComponentProps<"dd">) {
return (
<dd
data-slot="stat-value"
className={cn("text-3xl font-semibold tracking-tight tabular-nums", className)}
{...props}
/>
);
}
interface StatCaptionProps extends ComponentProps<"dd"> {
/** Adds an arrow for the direction of change and exposes it as `data-trend`. */
trend?: StatTrend;
/**
* Read to screen readers before the caption, since the arrow is only a
* picture. Defaults to "Increase", "Decrease" or "No change".
*/
trendLabel?: string;
}
/**
* A note under the value: a comparison, a period. With `trend` it gets an
* arrow icon (hidden from screen readers, which hear `trendLabel` instead).
* Up is tinted `text-success` and down `text-destructive`; restyle through
* `data-trend` where a rise is bad news.
*/
function StatCaption({ className, trend, trendLabel, children, ...props }: StatCaptionProps) {
const Icon = trend ? TREND_ICONS[trend] : null;
return (
<dd
data-slot="stat-caption"
data-trend={trend}
className={cn(
"text-muted-foreground flex items-center gap-1 text-xs",
"data-[trend=down]:[&>svg]:text-destructive data-[trend=up]:[&>svg]:text-success [&>svg]:size-3.5 [&>svg]:shrink-0",
className
)}
{...props}
>
{Icon ? <Icon aria-hidden="true" data-slot="stat-trend-icon" /> : null}
{trend ? <span className="sr-only">{`${trendLabel ?? TREND_LABELS[trend]}: `}</span> : null}
{children}
</dd>
);
}
export {
Stat,
StatCaption,
StatGroup,
StatLabel,
StatValue,
type StatCaptionProps,
type StatTrend,
};