KPI Sparkline Cards
Metric cards with a value that rolls in, an inline sparkline, and a rise/fall badge.
The KPI metric cards you see all over dashboards: when the card enters the window the big number rolls up from 0, an SVG sparkline draws the trend curve with a stroke animation and fades in the gradient below it, a percentage badge pops out in the top-right corner (green for up, red for down), and the cards reveal one after another with a stagger.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add kpi-sparkline-cards.enKPI Sparkline Cards is a React KPI stat card component built with Tailwind CSS, Motion and SVG. Each card has a large number that counts up, a small trend chart (sparkline) drawn as an SVG path and a percentage change badge, and the entrance animation only plays once the card scrolls into view.
Playground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<KpiSparklineCards/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
When to use
- The home dashboard of a SaaS admin or back office, listing core metrics such as revenue, subscriptions and conversion rate.
- Today or this-month sales summaries in a seller dashboard.
- Marketing performance reports that compare against the previous period.
- A metrics highlight section on a product site, such as user growth.
- Not a good fit: when clicking a metric should expand its details, use Drilldown Stat Grid.
Installation
npx webberui@latest add https://webberui.com/r/kpi-sparkline-cards.en.jsonOr, once registries are configured in components.json, install it as @webberui/kpi-sparkline-cards.
Usage
import { KpiSparklineCards } from "@/components/ui/kpi-sparkline-cards";
<KpiSparklineCards
items={[
{
label: "Revenue this month",
value: 1284500,
delta: 12.4,
prefix: "NT$",
series: [42, 48, 45, 52, 58, 63, 70, 76],
},
{
label: "Conversion rate",
value: 3.62,
delta: -1.8,
suffix: "%",
decimals: 2,
series: [52, 50, 47, 44, 41, 38],
},
]}
/>Examples
Building cards from server data
The exported KpiSparklineItem type can describe your API data directly; map it in a Server Component and pass the result to items.
// app/dashboard/page.tsx
import {
KpiSparklineCards,
type KpiSparklineItem,
} from "@/components/ui/kpi-sparkline-cards";
import { getMetrics } from "@/lib/metrics";
export default async function DashboardPage() {
const m = await getMetrics(); // e.g. { revenue: { total, change, daily: number[] }, ... }
const items: KpiSparklineItem[] = [
{ label: "Monthly revenue", value: m.revenue.total, delta: m.revenue.change, prefix: "$", series: m.revenue.daily },
{ label: "Paid subscribers", value: m.subscribers.total, delta: m.subscribers.change, series: m.subscribers.daily },
{ label: "Average order value", value: m.aov.total, delta: m.aov.change, prefix: "$", series: m.aov.daily },
];
return <KpiSparklineCards items={items} className="xl:grid-cols-3" />;
}Decimals and units
decimals sets the number of decimal places, and prefix / suffix add a currency symbol or unit; when delta is 0, the badge and line turn neutral gray.
<KpiSparklineCards
items={[
{ label: "Conversion rate", value: 4.28, delta: 0.6, suffix: "%", decimals: 2, series: [3.1, 3.4, 3.6, 3.9, 4.0, 4.28] },
{ label: "Avg. response time", value: 182, delta: -8.5, suffix: " ms", series: [240, 226, 210, 198, 190, 182] },
{ label: "NPS", value: 46, delta: 0, series: [45, 47, 46, 44, 46, 46] },
]}
/>Columns and animation timing
The container is a sm:grid-cols-2 xl:grid-cols-4 grid, and className is merged with cn, so you can override the column count and gap. countDuration, drawDuration and stagger set the seconds for the count-up, the line drawing and the delay between cards.
<KpiSparklineCards
items={items}
className="gap-6 lg:grid-cols-2 xl:grid-cols-2"
countDuration={2}
drawDuration={1.6}
stagger={0.2}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
items | KpiSparklineItem[] | — | The data for the KPI metric cards |
countDuration | number | 1.4 | Duration of the big number rolling up (seconds) |
drawDuration | number | 1.1 | Duration of the sparkline stroke animation (seconds) |
stagger | number | 0.12 | Interval between cards revealing in order (seconds) |
className | string | — | Forwarded to the outer grid container |
KpiSparklineItem fields:
| Field | Type | Default | Description |
|---|---|---|---|
label | string | — | Metric name, shown in the card's top-left corner |
value | number | — | Metric value; on reveal it rolls up from 0 to this value |
delta | number | — | Percentage change against the previous period: green when positive, red when negative, neutral grey at 0 |
series | number[] | — | The data series for the mini trend line (at least 6 points recommended) |
prefix | string | — | Prefix before the value, for example "NT$" |
suffix | string | — | Suffix after the value, for example "%" |
decimals | number | 0 | Number of decimal places shown for the value |
Accessibility
- When the user has "reduce motion" enabled at the system level, the numbers show their final value directly and the line appears complete immediately, with no rolling or stroke animation
- Each card provides a full
sr-onlysummary (metric name, final value, and direction of change), while the animating numbers and the sparkline are hidden from assistive technology (aria-hidden), which avoids announcing the intermediate values mid-roll - Rise and fall are not conveyed by color alone: the badge also expresses the trend with a directional arrow icon and the percentage in text
- The big numbers use
tabular-nums, so the layout width stays stable and does not jitter while they roll up
FAQ
Can I use it in a Next.js App Router Server Component?
Yes. The component file already starts with "use client", so a Server Component page can import it and pass items, which only contain strings, numbers and number arrays and serialize cleanly. The number always renders as 0 on first render, so server and client output match and there is no hydration mismatch.
How are numbers formatted?
Numbers get thousands separators: the Chinese version of the component uses Intl.NumberFormat("zh-TW") and the English version uses Intl.NumberFormat("en-US"), with decimal places from each item's decimals (default 0).
Is a drop always red? What about metrics where lower is better, like bounce rate?
Color depends only on the sign of delta: positive is green, negative is red and 0 is gray. There is no prop to invert it; for lower-is-better metrics, edit the tone logic in the source or make the direction clear in the label.
Does the animation replay when data updates?
The card entrance and line drawing play only the first time the card enters the viewport (useInView with once: true). If value changes afterwards, the number counts up again from 0 to the new value.
Does it support reduced motion and screen readers?
With "reduce motion" enabled, the final number shows immediately and the line and badge appear without animation. Each card has an sr-only summary with the label, final value and trend direction, while the animating number is hidden from assistive technology. Dependencies are motion, lucide-react, clsx and tailwind-merge.