Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

Charts & Stats

Stat Card

Charts & Stats

Stat Card React chart component for Tailwind CSS v4

SparklineTracker
Live props
Content
label
value
format
currency
decimals
prefix
suffix
delta
deltaFormat
deltaLabel
period
info
goal
emptyValue
State
trend
invertTrend
loading
selected
Appearance
size
variant
radius
tone
color
iconTone
symbolDisplay
flatDelta
sparklineType
durationDisplay
Behavior
animation
duration900ms
locale
href
Code
import { StatCard } from "@/components/ui/stat-card"

<StatCard />
  1. Home
  2. Components
  3. Charts & Stats
  4. Stat Card
Pro

Stat Card React chart component for Tailwind CSS v4

v1.1.0·Updated in 1.2.0

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.

It is read, not operated. The first time a card scrolls into view its number counts up from zero (900ms, easing out), the delta pill fades in and rises 4px just after, and the sparkline draws from left to right; later value changes count from the number on screen, and a change that arrives mid-count retargets without a jump. The server and the first paint already show the final numbers, and screen readers hear one sentence with the final value and the change ("$128,450, up 12.4% vs. last month"), never the numbers in between. The delta is green when the change is good and red when it is bad; with invertTrend a drop in costs or response time is green. The info icon opens its explanation on hover, on keyboard focus, and on a tap or long press on phones (a 44px hit area); Escape closes it. With href or onClick the whole card is one link or button (Tab to it, Enter to open): it dips to 0.98 while pressed, lifts on hover, and shows a chevron (always visible on touch); the info button and the action slot stay separately clickable. In a StatGroup the tiles count up 60ms apart (capped at 12 tiles); on phones the group is two columns and md values shrink to fit 360px. Under reduced motion numbers and charts appear final at once. In the playground press Simulate a new day: a new day joins the sparkline and the value and delta count to their new numbers.

Stat Card is a Pro React chart component in the React chart and stat components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add stat-card, together with Animated Counter, Progress, Skeleton, Tooltip, useLocale and 4 more and the npm packages motion, d3-scale, d3-shape and lucide-react.

Add to a project

Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup

npx wingo-ui@latest add stat-card

Stat Card is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans

Usage

Import StatCard from @/components/ui/stat-card.

import { Clock, FileText, UserPlus, Wallet } from "lucide-react"
import { StatCard, StatGroup } from "@/components/ui/stat-card"

export function DashboardKpis({ daily }: { daily: number[] }) {
  return (
    <StatGroup variant="joined">
      <StatCard
        label="Revenue"
        icon={<Wallet />}
        value={128450}
        format="currency"
        delta={12.4}
        deltaLabel="vs. last month"
        sparkline={daily}
        goal={190000}
        locale="en-US"
       
      />
      <StatCard label="Invoices issued" icon={<FileText />} value={342} delta={8} deltaFormat="number" locale="en-US" />
      <StatCard label="New clients" icon={<UserPlus />} value={27} delta={-3.1} locale="en-US" />
      <StatCard
        label="Average time to payment"
        icon={<Clock />}
        value={18 * 86400}
        format="duration"
        delta={-2.5}
        formatDelta={(d) => `${d.toLocaleString("en-US")} days`}
        invertTrend
        locale="en-US"
       
      />
    </StatGroup>
  )
}

Props

  • labeldefault ""
    ReactNode

    What the number is, in the header; truncates with a title on narrow tiles. When empty the playground shows a sample that follows format (Revenue for currency).

  • valuedefault 128450
    number | string | null

    The number. It counts up and is formatted by format; a string renders as is (no animation); null or no value shows emptyValue. The playground starts at a sample 128450; the component's default is null.

  • formatdefault "number"
    "number" | "currency" | "percent" | "compact" | "duration" (StatValueFormat)

    How the number reads, in the locale: 128,450, $128,450 (see symbolDisplay), 12.4% (value 12.4), 128.5K, or a duration from seconds in its largest two units ("2 hr, 15 min", "18 days").

  • currencydefault "USD"
    string

    ISO 4217 code for format="currency" and deltaFormat="currency", printed with its narrow symbol ("$", "€", "£").

  • decimalsdefault "auto"
    "auto" | "0" | "1" | "2" | "3" | number

    Fraction digits of the value. auto: 0 for numbers from 100 up, the currency's digits below 1.000 (0 above), 1 for percent and compact. Fixed from the final value, so the count never changes width mid-way.

  • prefixdefault ""
    ReactNode

    Text before the number for units format does not know, set smaller and muted ("≈").

  • suffixdefault ""
    ReactNode

    Text after the number ("km", "/mo", "pcs").

  • deltadefault 12.4
    number | null

    The change vs the previous period, in deltaFormat units (12.4 is +12.4% with percent). Its sign sets the arrow; |delta| below 0.05 is flat. The playground's sample is 12.4; the component has none (no pill).

  • deltaFormatdefault "percent"
    "percent" | "number" | "currency" | "duration" (StatDeltaFormat)

    Unit of delta: +12.4%, +8, +$1,250, or seconds as a duration (+2 days, 12 hr).

  • trenddefault "auto"
    "auto" | "up" | "down" | "flat"

    The arrow. auto reads the sign of delta; set it to force a direction (a pill with only an arrow when there is no delta). For your own delta badges and tables the helpers the card uses are exported too (see Parts).

  • deltaLabeldefault ""
    ReactNode

    The comparison after the pill, like "vs. last month". Also read in the spoken sentence.

  • perioddefault ""
    ReactNode

    A quiet caption at the end of the header, like "Sept. 2026".

  • infodefault ""
    ReactNode

    What the metric means, on an info button beside the label: a tooltip on hover or focus, and on a tap on touch screens.

  • goaldefault 190000
    number

    Adds a goal bar (value / goal) with "68% of goal" and the formatted target. Numbers only; 0 or less hides it. The playground's sample is 190000; the component has none.

  • emptyValuedefault "–"
    ReactNode

    Shown, muted, when value is null or missing. The playground's readout shows it; clear value in code to see it in the card.

  • sizedefault "md"
    "sm" | "md" | "lg"

    sm: compact (20px value, p-3, the sparkline at the end of the value row, 48 x 20); md: 28px value, p-4 / p-5, a 40px chart; lg: the hero (32px, 48px from 768px up, p-5 / p-6, a 64px chart).

  • variantdefault "surface"
    "surface" | "card" | "outline" | "plain"

    surface: a raised white panel (dark: #1A1A1A) with the raised shadow; card: a flat gray tile; outline: a hairline border; plain: no container and no padding, for cards inside your own panel.

  • radiusdefault "md"
    "none" | "sm" | "md" | "lg"

    Corners: 0, 12px, 16px or 24px.

  • tonedefault "neutral"
    "neutral" | "brand" | "success" | "warning" | "danger" | "info"

    Accent of the icon tile, the sparkline and the goal bar. The delta never takes it: it is green or red by meaning.

  • colordefault ""
    string

    Any CSS color; replaces the tone for the icon tile, sparkline and goal bar.

  • iconTonedefault ""
    "neutral" | "brand" | "success" | "warning" | "danger" | "info"

    The icon tile's own tint, independent of tone: a green Pro tile or an orange revenue tile over a neutral sparkline. Unset, the tile follows tone.

  • symbolDisplaydefault "inline"
    "inline" | "muted" (StatSymbolDisplay)

    The currency symbol of format="currency": at the number's size and weight ("$0", "$128,450"), or smaller and muted beside it.

  • flatDeltadefault "label"
    "label" | "value" | "hidden" (StatFlatDelta)

    How a change under 0.05 reads: a neutral "No change" pill (labels.unchanged), the zero value ("0%"), or no pill at all. A null delta never shows a pill.

  • sparklineTypedefault "area"
    "line" | "area" | "bar"

    The built-in sparkline: a line, a line over a soft gradient, or bars with the last one highlighted.

  • durationDisplaydefault "short"
    "long" | "short" | "narrow"

    Unit words for format="duration" and deltaFormat="duration": 2 ore 15 minute, 2 ore 15 min., 2 h 15 m.

  • invertTrenddefault false
    boolean

    Down is good: costs, churn, response time. A drop turns green and a rise red; the arrow still points the real way.

  • loadingdefault false
    boolean

    Skeletons with the real sizes for the value, delta, chart and goal; the label stays. aria-busy is set. When it turns false the number counts up.

  • animationdefault "count"
    "count" | "roll" | "none"

    count: tweens from 0 the first time the card is seen, then from the previous value; roll: the AnimatedCounter odometer for live numbers (numbers and currency; changes roll, the first paint is final); none: static.

  • durationdefault 900
    number

    Milliseconds of the count (roll uses 70% of it per digit).

  • localedefault ""
    string

    Intl locale for every number. Empty uses the UIProvider locale, then the page language. The demo falls back to en-US.

  • icon
    ReactNode

    The header icon, in a tile: gray when neutral, tinted with the tone otherwise.

  • sparkline
    number[]

    Values for the built-in sparkline, oldest first. One point draws a dot; equal values draw a flat line. The chart alone is exported as Sparkline (see Parts).

  • chart
    ReactNode

    Replaces the sparkline with your own chart, in the same spot.

  • goalLabel
    (percent: number) => ReactNode

    The goal bar caption; defaults to labels.goal ("68% of goal").

  • formatValue
    (value: number) => string

    Full control over the number text. Called on every frame of the count, so keep it cheap.

  • formatDelta
    (delta: number) => string

    The delta text in place of deltaFormat. It gets |delta|; the sign is added (d => `${d} days`).

  • action
    ReactNode

    A control at the end of the header (a ⋯ menu button). It stays clickable on pressable cards.

  • hrefdefault ""
    string

    Makes the whole card a link: press, hover lift, focus outline and a chevron hint (always shown on touch). Its accessible name is the label plus the spoken value and delta. The info button and the action slot stay clickable on top. The demo's link does not navigate.

  • selecteddefault false
    boolean

    For a pressable card that filters or picks something (a status tile over a table): a 2px ring in the tone, aria-pressed on the button (onClick) or aria-current on the link (href). Leave it undefined on plain links.

  • linkProps
    Omit<ComponentProps<"a">, "href" | "onClick" | "children" | "className">

    Extra attributes for the link that covers an href card: target, rel (target="_blank" gets noopener), download, data-*.

  • onClick
    (event: MouseEvent<HTMLElement>) => void

    Makes the whole card a button (or runs alongside href).

  • footer
    ReactNode

    A row under a hairline that runs edge to edge ("View report →").

  • children
    ReactNode

    Extra content under the numbers, above the chart.

  • labels
    Partial<StatCardLabels>

    Strings: increase / decrease (text => "Up 12.4%"), unchanged ("No change"), goal (percent => "68% of goal"), info ("About this metric"), loading. The English defaults are exported (see Parts).

  • classNames
    Partial<Record<"header" | "icon" | "label" | "info" | "period" | "action" | "value" | "unit" | "delta" | "deltaLabel" | "chart" | "goal" | "footer", string>>

    Extra classes for the inner parts.

  • className
    string

    Extra classes for the card, merged last. For a row of cards use StatGroup (see Parts).

Parts

  • StatGroup

    component

    A row of stat cards (role list): a responsive grid of separate tiles, or one joined panel with hairlines between them.

    • columns
      { base?: 1 | 2; sm?: 2 | 3; lg?: 2 | 3 | 4 }

      Columns per breakpoint: 2 on phones and 4 from 1024px by default.

    • variantdefault "separate"
      "separate" | "joined"

      Separate tiles, or one panel with hairlines between tiles.

    • size
      "sm" | "md" | "lg"

      Passed to every card that does not set its own.

    • animation
      "count" | "roll" | "none"

      Passed to every card that does not set its own.

    • staggerdefault true
      boolean

      Tiles count up 60ms apart, capped at 12 tiles.

    • radiusdefault "md"
      "none" | "sm" | "md" | "lg"

      The joined panel's corners.

  • Sparkline

    component

    The card's chart on its own, for table cells and list rows. Strokes stay 1.5px at any size, the area gets a soft gradient and it draws in when it scrolls into view; aria-hidden.

    • data*
      number[]

      The values, oldest first.

    • typedefault "area"
      "line" | "area" | "bar"

      A line, a line with a soft fill below it, or bars.

    • width
      number | string

      Any CSS length or px; fills the container by default.

    • height
      number

      px; 32 / 40 / 64 by size when unset.

    • sizedefault "md"
      "sm" | "md" | "lg"

      Picks the default height.

    • tonedefault "neutral"
      Tone

      The stroke and fill color from the design tokens.

    • color
      string

      Any CSS color; replaces the tone.

    • curvedefault "monotone"
      "monotone" | "linear"

      Smooth or straight segments.

    • showLastdefault true
      boolean

      A dot on the last point (bars: the last bar in full color).

    • animateddefault true
      boolean

      Draws in once when it scrolls into view; off under reduced motion.

  • getStatTrend

    function

    getStatTrend(delta, trend?) returns the arrow direction: "up", "down", "flat", or null when there is no delta.

  • getStatIntent

    function

    getStatIntent(direction, invert?) returns "positive", "negative" or "neutral", flipped when a drop is good (costs, response time).

  • formatStatDuration

    function

    formatStatDuration(seconds, locale, display?) formats a duration the way the card does, in "long", "short" or "narrow" units.

  • DEFAULT_STAT_CARD_LABELS

    constant

    The English strings: increase and decrease ("Up 12.4%"), "No change", the goal line, the info button and loading.

Examples

Dashboard row

A joined StatGroup of four KPIs with a period switch; tiles count up 60ms apart.

Sizes

sm, md and lg: compact tiles, the default, and the hero number.

Variants

surface, card, outline and plain, on the page and on a gray card.

Loading

Skeletons with the real sizes, then the numbers count up.

Phone grid

Compact cards two per row at 390px with bar sparklines.

Live numbers

animation="roll": an odometer for values that keep changing.

Pressable cards

href or onClick makes the whole card a link or button, with a footer.

Tones and formats

Brand, info, success and a custom color; percent, compact and duration values.

Dependencies

motiond3-scaled3-shapelucide-reactanimated-counterprogressskeletontooltipuse-localemotionnumber-formattonesui-config

Files

  • components/ui/stat-card.tsx

FAQ

Is the React Stat Card chart component free?

The preview, props and docs are free; copying the source needs a Pro plan. See plans.

How do I install Stat Card?

Run npx wingo-ui@latest add stat-card. It also adds the Wingo UI items it builds on (Animated Counter, Progress, Skeleton, Tooltip, useLocale and 4 more) and the npm packages motion, d3-scale, d3-shape and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/stat-card.

How does Stat Card behave on phones?

The info icon opens its explanation on hover, on keyboard focus, and on a tap or long press on phones (a 44px hit area); Escape closes it. In a StatGroup the tiles count up 60ms apart (capped at 12 tiles); on phones the group is two columns and md values shrink to fit 360px.

See also

ProgressAnimated CounterSparklineTrend badgeCard

Used in

Admin Dashboard PageKPI Dashboard

More React chart and stat components

LeaderboardArea chartBar chartDonut chartFunnel chartLine chart
All 16 React chart and stat components

Articles using this component

  • React Funnel Chart: Conversion, Drop-off and Cohorts

    Component guides · Nov 6, 2026 · 9 min read

  • Shadcn KPI Cards: Deltas, Sparklines and Goal Bars

    Component guides · Nov 6, 2026 · 9 min read

  • React Animated Number Counter: Rolling Odometer Digits

    Component guides · Oct 19, 2026 · 9 min read

  • React Sparkline: Table Cells, KPI Tiles, Delta Chips

    Component guides · Oct 19, 2026 · 9 min read

PreviousSparklineNext Tracker
Unique visitors
128,450
+12.4%
128,450, Up 12.4%
68% of goal190,000

Add a day: the value and the delta count from where they are. en-US · count 900ms · trend auto · empty "–" · +0 days