Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
  1. Home
  2. Blog
  3. Component guides
  4. Shadcn KPI Cards: Deltas, Sparklines and Goal Bars
Auf Deutsch lesen
  1. Blog
  2. Component guides
  3. Shadcn KPI Cards: Deltas, Sparklines and Goal Bars
Component guides
Component guides

Shadcn KPI Cards: Deltas, Sparklines and Goal Bars

SR
Serban Rusu · Founder of Wingo UI
Nov 6, 2026 · 9 min read

On this page

0%
  1. What should a KPI card show?
  2. What do the shadcn dashboard cards give you?
  3. How do I build a KPI card with Stat Card?
    1. Does the count-up hurt SEO or screen readers?
    2. How does the delta know good from bad?
    3. When do I use the standalone Sparkline?
  4. How do I lay out a row of React KPI cards?
  5. What does a full React KPI dashboard look like?
  6. How does the KPI grid collapse on phones?
  7. What are the common KPI card mistakes?
  8. What should I do next?
  9. FAQ
    1. Does shadcn/ui have a KPI card component?
    2. How do I show a decrease as good in a React KPI card?
    3. How many KPI cards should a dashboard show?
    4. How should KPI cards behave on mobile?
    5. Should KPI numbers animate?

TL;DR

A good shadcn KPI card tells the reader at a glance whether a number is good, which takes five parts: a clear label, a formatted number, a delta colored by meaning (a drop in churn is green), a sparkline for the shape of the period and a goal bar for the target. shadcn/ui has no KPI card component, and its dashboard-01 block ships static cards with hardcoded values and an outline badge that looks the same for gains and losses. The Wingo UI Stat Card, Trend Badge, Sparkline and KPI Dashboard (all Pro) handle formatting, meaning, count-up and the two-column phone grid through props.

Published Nov 6, 2026

You open a dashboard, see "$128,450" in a tile and still do not know if the month went well. That is the gap most shadcn KPI cards leave open: a number, a label and maybe a badge, with no sense of direction, shape or target. This guide shows what a KPI card needs to answer "is this good?", what the official shadcn block gives you, and how to build shadcn stat cards with the Wingo UI Stat Card, Trend Badge and Sparkline. Then we put them into the full KPI Dashboard block and look at how its grid collapses on phones.

What should a KPI card show?

A KPI card earns its place when someone can read it in two seconds and know what to do next. That takes five parts, each answering a smaller question:

  1. The label and an explanation. What is this number? "Revenue" is fine; "Net revenue, before tax" behind an info icon is better, because two people on the same team often mean different things by the same word.
  2. The number, formatted for the reader. $128,450, 2.1%, 18 days, 128.5K. The format belongs to the metric, and the locale belongs to the reader.
  3. The change and its meaning. +12.4% vs. last month, green when the change is good, red when it is bad. The color has to follow the meaning, not the sign: a drop in churn is good news.
  4. The shape of the period. A sparkline shows whether a +12% month was a steady climb or one big invoice on the last day.
  5. The target. A goal bar ("68% of goal") turns a number into progress.

Most cards stop after the first two or three. The last two are what make a row of React KPI cards useful instead of decorative.

What do the shadcn dashboard cards give you?

shadcn/ui has no KPI or stat card component (checked November 2026). The closest thing is the SectionCards file inside the dashboard-01 block (source on GitHub (opens in a new tab), read November 6, 2026). It composes Card, CardHeader, CardDescription, CardTitle, CardAction, CardFooter and Badge, and it is a good starting point for a shadcn analytics dashboard:

  • Each card has a label, a hardcoded value like $1,250.00, an outline Badge with a trend icon and a text like +12.5%, and a two-line footer ("Down 20% this period", "Acquisition needs attention").
  • The badge looks the same for an increase and a decrease. Only the icon changes, so "-20%" on New Customers is not visually marked as bad.
  • There is no sparkline, no goal and no count-up. Values are strings in JSX, so formatting is up to you.
  • The grid is one column by default, two from the @xl container width and four from @5xl.

That is the shadcn way: a pattern you copy and own, and community block collections follow the same copy-and-own model. It works well for a fixed set of four tiles. Once the numbers come from an API, you end up writing the formatting, the good-or-bad color per metric and the loading state yourself, in every card. A component with props moves that logic into one place.

How do I build a KPI card with Stat Card?

Install stat-card, then pass a numeric value, a format, a delta, the sparkline data and a goal: one element covers all five parts. The CLI brings its dependencies along (the counter, progress bar, tooltip and number helpers):

bash
npx wingo-ui@latest add stat-card

value is a number, format decides how it reads, delta is the change in deltaFormat units (percent by default), and goal adds the progress bar:

tsx
import { Wallet } from "lucide-react";
import { StatCard } from "@/components/ui/stat-card";
const daily = [3900, 4100, 3800, 4500, 4700, 4200, 5100, 5300, 4900, 5600, 5800, 6100];
export function RevenueCard() {
return (
<StatCard
label="Revenue"
info="Invoices issued, before tax"
icon={<Wallet />}
period="Sept. 2026"
value={128450}
format="currency"
currency="USD"
delta={12.4}
deltaLabel="vs. last month"
sparkline={daily}
goal={190000}
locale="en-US"
/>
);
}
Press Simulate a new day: a new day joins the sparkline and the value and the delta count from the numbers on screen to the new ones
$ npx wingo-ui@latest add stat-card
ProStat Card docs

Does the count-up hurt SEO or screen readers?

No, because the animation only runs on the client. The server HTML and the first paint already contain the final number, so a crawler or a slow phone sees $128,450, not $0. The number counts up from zero once, the first time the card scrolls into view (900ms by default, set with duration), and later changes count from the number on screen. Screen readers hear one sentence with the final value and the change, such as "$128,450, up 12.4% vs. last month", never the numbers in between. Under reduced motion the numbers and charts appear final at once. For live numbers that tick, animation="roll" uses an odometer instead, and animation="none" turns it off.

How does the delta know good from bad?

The arrow follows the sign of delta; the color follows the meaning. By default a rise is green and a fall is red. Add invertTrend for metrics where down is good, like costs, churn or response time: a drop turns green and the arrow still points down. The tone prop colors the icon tile, the sparkline and the goal bar, but never the delta, so a brand-orange card cannot accidentally make bad news look neutral.

The same logic is a standalone component, TrendBadge, for table cells, leaderboards and your own cards:

tsx
"use client";
import { TrendBadge } from "@/components/ui/trend-badge";
export function ChangeCells() {
return (
<>
<TrendBadge value={12.4} label="vs. last month" locale="en-US" />
<TrendBadge value={-0.4} invertTrend formatValue={(v) => `${v < 0 ? "−" : "+"}${Math.abs(v)} pts`} />
</>
);
}

The file needs "use client" because formatValue is a function, and functions cannot cross from a Server Component into a Client Component as props.

Press Change to cycle the value: with invertTrend on, a rise turns red and a drop green, while the arrow keeps pointing the real way
$ npx wingo-ui@latest add trend-badge
ProTrend badge docs

When do I use the standalone Sparkline?

Use the standalone one when a reader needs to inspect the points, or when the chart lives outside a card. Stat Card has a built-in sparkline (sparkline for the data, sparklineType for line, area or bar), and stat-card.tsx also exports that chart as Sparkline for simple cases. The separate Sparkline component goes further: win / loss ticks (type="win-loss"), min and max markers (showMinMax), a dashed reference line at the first value, the average or zero, trendColor to color by direction, and interactive mode where you hover, drag a finger or use the arrow keys to read each point. Use it in table rows, or pass it to a card through the chart prop when you need those extras:

tsx
import { Sparkline } from "@/components/ui/sparkline";
export function RevenueTrend() {
return (
<Sparkline
data={[8200, 8650, 8400, 9100, 9480, 10150, 10600, 11420, 12480]}
valueFormat="currency"
label="Revenue, last 9 weeks"
trendColor
interactive
locale="en-US"
/>
);
}

Both files export a component named Sparkline, so import the standalone one from @/components/ui/sparkline and alias it if a file needs both.

How do I lay out a row of React KPI cards?

Wrap the cards in StatGroup. It is a list (role="list") with responsive columns: two on phones and four from 1024px by default, changed with columns={{ base, sm, lg }}. The tiles count up 60ms apart (turn it off with stagger={false}), and variant="joined" draws one panel with hairlines between the tiles instead of separate cards.

tsx
"use client";
import { Clock, Repeat, UserMinus, Users } from "lucide-react";
import { StatCard, StatGroup } from "@/components/ui/stat-card";
export function SaasKpis({ mrr, active }: { mrr: number[]; active: number[] }) {
return (
<StatGroup variant="joined">
<StatCard label="MRR" icon={<Repeat />} value={42800} format="currency" delta={6.2} sparkline={mrr} goal={50000} locale="en-US" />
<StatCard label="Active users" icon={<Users />} value={3214} delta={4.8} sparkline={active} locale="en-US" />
<StatCard
label="Churn"
icon={<UserMinus />}
value={2.1}
format="percent"
delta={-0.4}
formatDelta={(d) => `${d} pts`}
invertTrend
locale="en-US"
/>
<StatCard
label="First response time"
icon={<Clock />}
value={4 * 3600}
format="duration"
delta={-1800}
deltaFormat="duration"
invertTrend
locale="en-US"
/>
</StatGroup>
);
}

Three details in that code matter. The file is a Client Component because formatDelta is a function prop. Churn uses formatDelta because a change in a percentage is percentage points, not percent: going from 2.5% to 2.1% is "−0.4 pts", and printing "−0.4%" would be wrong. Durations take seconds, so format="duration" prints 4 hr and the delta prints 30 min. Pass loading while the data is on its way and each card shows skeletons at the real sizes, so nothing jumps when the numbers land.

What does a full React KPI dashboard look like?

A full dashboard puts the KPI tiles on top and the widgets under them, and the KPI Dashboard block ships that whole screen: revenue against the previous period, revenue by month or city, sources, a funnel, top sellers, the latest invoices and an activity feed. The header has a period picker with presets and a compare switch that turns the dashed previous series and every delta on and off. Refresh and export buttons appear when you pass onRefresh and onExport.

bash
npx wingo-ui@latest add kpi-dashboard
tsx
"use client";
import { useState } from "react";
import { KpiDashboard, KPI_DASHBOARD_SAMPLE } from "@/components/ui/kpi-dashboard";
import type { DateRange } from "@/lib/date-utils";
export function SalesDashboard() {
const [period, setPeriod] = useState<DateRange>({ from: new Date(2026, 8, 1), to: new Date(2026, 8, 30) });
return (
<KpiDashboard
title="Dashboard"
description="Northwind Software Inc. · Portland"
data={KPI_DASHBOARD_SAMPLE}
period={period}
onPeriodChange={setPeriod}
onRefresh={() => fetch("/api/dashboard")}
widgetLinks={{ invoices: "/invoices", activity: "/activity" }}
locale="en-US"
/>
);
}

The block shows data; fetching it for a period is your app's job. onRefresh can return a promise: the button spins until it settles and then announces the update time. Each item in data.kpis is a KpiItem with the same ideas as Stat Card: value, format, delta, deltaFormat (including points for percentage points), invertTrend, sparkline, goal, info and href. In the sample, Outstanding is $31,650 with a +4.1% delta and invertTrend, so the rise in money owed shows red.

Pick a period, flip the compare switch and watch the deltas and the dashed series go away, then narrow the window to see the KPI tiles go two per row
$ npx wingo-ui@latest add kpi-dashboard
ProKPI Dashboard docs

How does the KPI grid collapse on phones?

The dashboard measures its own width with container queries, not the viewport, so a sidebar never squeezes it into a broken layout. With the default kpiColumns={0} (auto):

Dashboard widthKPI tiles per rowWidgets
below 576px2, and an odd last tile stretches to the full rowone column
576px to 671px3 (2 when there are exactly 4), leftover tiles stretchone column
672px to 1023px3 (2 for 4 tiles)two columns
1024px and upevery tile in one row, up to 6 (more wrap into rows)two columns, three from 1152px

The latest invoices switch from a table to cards whenever their widget is narrower than 672px. On a phone the header actions also fold: refresh and export move into one ⋯ button that opens an action sheet, and the period picker opens as a bottom sheet instead of a popover. StatGroup alone uses viewport breakpoints (two columns base, four from lg), and at the md size the values shrink so a currency figure still fits a 360px screen. Two per row is the right phone default: one per row turns four KPIs into a full screen of scrolling, and three leaves each tile about 100px of a 360px screen for a figure like $128,450.

What are the common KPI card mistakes?

Most mistakes make a card look precise while it misleads the reader:

  • Coloring by sign. Red for every drop makes "costs down 8%" look like a problem. Decide per metric whether down is good.
  • Comparing different periods. "+12% vs. last month" on September 15 compares half a month with a full one. Compare with the same days of the previous period, or label the partial period.
  • Mixing percent and points. A conversion rate going from 20% to 23% is +3 points, or +15%. Pick one and say which.
  • Showing 0 when there is no data. null should print a dash ("–"), not $0, which reads as a real and very bad month.
  • Counting up on every refresh. Animate once; afterwards move from the old number to the new one, or people stop trusting the figures.

For the chart side of a dashboard, see React charts with Tailwind and, for sparklines in table cells on one shared scale, React sparkline, and for a whole admin app with tables and settings around these cards, the Next.js admin dashboard tutorial. If you are still choosing a library for the whole dashboard, read the best React component library for dashboards. More guides like this one are in component guides, and the React form components guide is the pillar for that series.

What should I do next?

Open the Stat Card docs and try the props in the playground; the docs, props and live demos are free to use without an account. Stat Card, Trend Badge, Sparkline and the KPI Dashboard are part of Pro ($8 a month, $80 a year or $150 once, see pricing), and a free account gets you the source of every free item if you want to start with the basics.

Components in this post

  • Stat Card

    A React stat card for dashboards: a label, a big number that counts up, a delta pill that knows good from bad, a sparkline and a goal bar.

    Pro
  • KPI Dashboard

    A React KPI dashboard: metric tiles with deltas, revenue against the last period, sources, funnel, top sellers, invoices and activity.

    Pro
  • Trend badge

    A React trend badge, the delta chip for KPIs, table cells and leaderboards: an arrow, a signed change and a color that says if it is good.

    Pro
  • Sparkline

    A React sparkline: a word-sized chart for table cells, list rows, KPI tiles and running text, as a line, an area, bars or win / loss ticks.

    Pro

FAQ

Does shadcn/ui have a KPI card component?

No. As of November 2026 shadcn/ui has no KPI or stat card component. Its dashboard-01 block includes a SectionCards file that composes Card and Badge with hardcoded numbers, and you edit that file for your own metrics.

How do I show a decrease as good in a React KPI card?

Separate the arrow from the color. The arrow follows the sign of the change, and the color follows the meaning: for costs, churn and response time a drop is positive. In Wingo UI that is the invertTrend prop on StatCard, TrendBadge and Sparkline.

How many KPI cards should a dashboard show?

Four to six in the top row is a common choice, because more tiles get too narrow to read on a laptop and push the charts below the fold. The KPI Dashboard puts up to six tiles in one row from 1024px of its own width and wraps more than that into several rows.

How should KPI cards behave on mobile?

Two cards per row is the usual phone layout: one per row wastes height, three gets too tight for a currency value at 360px. Make the last tile span the full row when the count is odd, shrink the number to fit, and keep the delta and label readable.

Should KPI numbers animate?

A short count-up on first view helps the eye land on the numbers, but the server HTML and screen readers should get the final value, and reduced motion should skip it. Later updates should count from the number on screen, not from zero.

Try it in your project

Stat Card

Pro

A React stat card for dashboards: a label, a big number that counts up, a delta pill that knows good from bad, a sparkline and a goal bar.

75 components free, no account needed. Pro from $8 a month.

Open Stat Card liveSee pricing
  • Dashboard
  • KPI
  • Charts
  • shadcn

Share

SR

About the author

Serban Rusu

Founder of Wingo UI

Serban Rusu founded Wingo UI. He builds its component library, CLI and MCP server, and writes about React interfaces for phones and AI coding agents.

More from Serban
Keep reading

Related posts

All posts
Component guides

React Sparkline: Table Cells, KPI Tiles, Delta Chips

Add a React sparkline to table cells and KPI tiles with one scale across rows, a delta chip that knows good from bad, keyboard scrubbing and touch support.

SRSerban Rusu·Oct 19, 2026·9 min read
Build it: screens and apps

Nextjs Admin Dashboard Tutorial: 5 Screens, Phone-Ready

A nextjs admin dashboard tutorial for the App Router: app shell, KPI home, server-side data tables, settings and team pages, each one usable on a phone.

SRSerban Rusu·Oct 9, 2026·23 min read
Component guides

React Funnel Chart: Conversion, Drop-off and Cohorts

Build a react funnel chart that prints each step's count, its conversion and the drop-off between steps, as aligned bars or a tapered shape that stays honest.

SRSerban Rusu·Nov 6, 2026·9 min read
Newsletter

Get new posts by email

New posts, product news and occasional offers from Wingo UI. We count clicks to keep it relevant.

No spam. Unsubscribe at any time.

WingoUI

Animated, configurable, mobile-first React components and blocks. Copy the source, make it yours, and let your coding agent build with it.

Product

  • Components
  • Templates
  • Pricing
  • AI agents
  • For teams
  • Theme generator

Resources

  • Docs
  • Contact
  • Programs
  • Blog
  • Changelog
  • Changelog RSS
  • llms.txt

Compare

  • Wingo UI vs paid kits
  • Wingo UI vs shadcn/ui
  • shadcn/ui alternatives
  • MUI alternatives
  • Tailwind Plus alternatives
  • Magic UI alternatives
  • Free components

Legal

  • Terms
  • Privacy
  • Refunds
  • License
  • Imprint

Component categories

  • Buttons & Actions
  • Inputs
  • Forms
  • Navigation
  • Overlays
  • Feedback
  • Data Display
  • Tables & Lists
  • Charts & Stats
  • Layout
  • Media
  • AI Kit
  • Text & Effects
  • Mobile
  • Commerce
  • Marketing Sections
  • Blocks
  • Hooks & Utilities

© 2026 Wingo UI

Wingo UI