Pre-alphaThe registry and the CLI are not published yet.Roadmap
Compositions

Stats Card

A metric, its label and icon, and its change against the last period.

This week

Revenue$12,480+12.4%vs last week
Spending$3,210+8%vs last week
Steps8,204−3%
Sessions1,0320%Same as last week

Spending went up, and that’s bad news: it’s red. The color follows whether the change is good, not only its direction.

This week

Revenue$12,480+12%Last 7 days
Visitors8,204-3%Last 7 days

The recent values as a line under the value. The line takes the color of the change: green, red, or the primary color when the trend is flat.

Goals

Steps8,204/ 10,00082%1,796 to go
Savings$5,400/ $5,000108%Goal reached

How far the value is from a goal. Past the goal, the bar stays full and turns green.

Today

Revenue$12,480+12.4%
Visitors8,204-3%
Orders4120%

Icon, label, value and change on one line. For a stack of metrics, or a dashboard that shows many at once.

Activity

70%Move420 kcalof 600 kcal
100%Stand12 hGoal reached

A ring for the share of a goal, the percentage in its middle. Past the goal, the ring turns green.

The variants are separate components with their own code: install the one you need.

Installation

npx axiom add stats-card
pnpm dlx axiom add stats-card
yarn dlx axiom add stats-card
bun x axiom add stats-card

Also copies: card, badge, icon, title, text and their dependencies. The file lands in components/compositions/stats-card.tsx. It brings no tokens of its own.

npx axiom add stats-card-sparkline
pnpm dlx axiom add stats-card-sparkline
yarn dlx axiom add stats-card-sparkline
bun x axiom add stats-card-sparkline

Also copies: card, badge, icon, title, text and their dependencies. The file lands in components/compositions/stats-card-sparkline.tsx. It brings no tokens of its own.

Needs react-native-svg, a native module: rebuild your development build after installing it.

npx axiom add stats-card-progress
pnpm dlx axiom add stats-card-progress
yarn dlx axiom add stats-card-progress
bun x axiom add stats-card-progress

Also copies: card, icon, title, text and their dependencies. The file lands in components/compositions/stats-card-progress.tsx. It brings no tokens of its own.

npx axiom add stats-card-inline
pnpm dlx axiom add stats-card-inline
yarn dlx axiom add stats-card-inline
bun x axiom add stats-card-inline

Also copies: card, badge, icon, title, text and their dependencies. The file lands in components/compositions/stats-card-inline.tsx. It brings no tokens of its own.

npx axiom add stats-card-ring
pnpm dlx axiom add stats-card-ring
yarn dlx axiom add stats-card-ring
bun x axiom add stats-card-ring

Also copies: card, title, text and their dependencies. The file lands in components/compositions/stats-card-ring.tsx. It brings no tokens of its own.

Needs react-native-svg, a native module: rebuild your development build after installing it.

Usage

import { StatsCard } from "@/components/compositions/stats-card";

<View style={{ flexDirection: "row", gap: 12 }}>
	<StatsCard
		label="Revenue"
		value="$12,480"
		icon="wallet"
		change="+12.4%"
		trend="up"
		caption="vs last week"
		style={{ flex: 1 }}
	/>
	<StatsCard
		label="Spending"
		value="$3,210"
		icon="cart"
		change="+8%"
		trend="up"
		positive={false}
		style={{ flex: 1 }}
	/>
</View>;
import { StatsCardSparkline } from "@/components/compositions/stats-card-sparkline";

<StatsCardSparkline
	label="Revenue"
	value="$12,480"
	icon="wallet"
	data={last7Days.map((day) => day.revenue)}
	change="+12.4%"
	trend="up"
	caption="Last 7 days"
	style={{ flex: 1 }}
/>;

data is scaled between its own minimum and maximum: the line shows the shape of the series, not its distance from zero. Two points at least; a dozen reads best at this size.

import { StatsCardProgress } from "@/components/compositions/stats-card-progress";

<StatsCardProgress
	label="Steps"
	value={formatNumber(steps)}
	goal={`/ ${formatNumber(goal)}`}
	progress={steps / goal}
	icon="flame"
	caption={`${formatNumber(goal - steps)} to go`}
/>;
import { StatsCardInline } from "@/components/compositions/stats-card-inline";

<View style={{ gap: 8 }}>
	<StatsCardInline label="Revenue" value="$12,480" icon="wallet" change="+12.4%" trend="up" onPress={openRevenue} />
	<StatsCardInline label="Refunds" value="$212" icon="cart" change="+4%" trend="up" positive={false} />
</View>;
import { StatsCardRing } from "@/components/compositions/stats-card-ring";

<StatsCardRing
	label="Move"
	value="420 kcal"
	progress={420 / 600}
	caption="of 600 kcal"
/>;

Pass ringLabel to replace the percentage in the ring, with something short: "3/5".

Values come formatted: the card never rounds or localizes a number.

Props

PropTypeDefaultDescription
labelstring—What is measured, muted, above the value.
valuestring—Already formatted, in large type. Shrinks to fit one line.
iconIconName—Before the label.
changestring—Change against the previous period, in a Badge: "+12.4%".
trend'up' | 'down' | 'flat''flat'Direction of the change. flat keeps the badge neutral.
positivebooleantrend !== 'down'Whether the change is good news: green if so, red if not. Pass false for spending or latency going up.
captionstring—After the change: "vs last week".
variant'elevated' | 'outlined' | 'filled''filled'The Card variant.
onPress() => void—Makes the card pressable, to open the detail.
styleStyleProp<ViewStyle>—Extra styles, usually flex: 1 in a row of cards.
PropTypeDefaultDescription
labelstring—What is measured, muted, above the value.
valuestring—Already formatted, in large type.
datanumber[]—The recent values, oldest first. Drawn as a 40pt line across the card.
iconIconName—Before the label.
changestring—In a Badge next to the value.
trend'up' | 'down' | 'flat''flat'Colors the badge and the line.
positivebooleantrend !== 'down'Whether the change is good news.
captionstring—Under the line: "Last 7 days".
variant'elevated' | 'outlined' | 'filled''filled'The Card variant.
onPress() => void—Makes the card pressable.
styleStyleProp<ViewStyle>—Extra styles, usually flex: 1.
PropTypeDefaultDescription
labelstring—What is measured.
valuestring—Already formatted.
goalstring—The target, muted after the value: "/ 10,000".
progressnumber—Share of the goal, from 0 to 1. Above 1, the bar stays full and turns green.
iconIconName—Before the label.
captionstring—Under the bar, on the right: "1,796 to go". The percentage is on the left.
variant'elevated' | 'outlined' | 'filled''filled'The Card variant.
onPress() => void—Makes the card pressable.
styleStyleProp<ViewStyle>—Extra styles for the card.
PropTypeDefaultDescription
labelstring—What is measured, muted, after the icon.
valuestring—Already formatted, on the right.
iconIconName—In a 32pt tile at the start of the row.
changestring—In a Badge after the value.
trend'up' | 'down' | 'flat''flat'Colors the badge.
positivebooleantrend !== 'down'Whether the change is good news.
variant'elevated' | 'outlined' | 'filled''filled'The Card variant.
onPress() => void—Makes the row pressable.
styleStyleProp<ViewStyle>—Extra styles for the card.
PropTypeDefaultDescription
labelstring—What is measured.
valuestring—Already formatted, next to the ring.
progressnumber—Share of the goal, from 0 to 1. Above 1, the ring stays full and turns green.
captionstring—Under the value: "of 600 kcal".
ringLabelstringthe percentageInside the 64pt ring.
variant'elevated' | 'outlined' | 'filled''filled'The Card variant.
onPress() => void—Makes the card pressable.
styleStyleProp<ViewStyle>—Extra styles for the card.

Accessibility

The card reads as one sentence: “Revenue: $12,480, +12.4%, vs last week”. The sparkline is hidden from screen readers: the value and the change already say what it shows.

Progress and Ring are progress bars: they announce their percentage along with the label, “Steps: 8,204, / 10,000, 82%, 1,796 to go”.

Built with

  • Card: the filled surface.
  • Title: the value.
  • Badge: the change, in success, error or neutral.
  • Icon and Text: the label and the caption.
  • react-native-svg: the line of Sparkline and the arc of Ring.

Used in

No block uses Stats Card yet.