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

Blocks

KPI Dashboard

Blocks

KPI Dashboard React block for Tailwind CSS v4

Invoice PageListing Detail Page
Live props
Content
title
description
revenueByLabel
Layout
layout
kpiColumnsAuto
revenueByOrientation
activityLimit5
Appearance
variant
radius
tone
color
currency
locale
State
defaultComparePrevious
loading
Behavior
animated
animateIn
refreshShortcut
Code
import { KpiDashboard } from "@/components/ui/kpi-dashboard"

<KpiDashboard />
  1. Home
  2. Components
  3. Blocks
  4. KPI Dashboard
Pro

KPI Dashboard React block for Tailwind CSS v4

v1.0.1·Updated in 1.2.0

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

Pick the period in the header: presets (Today, Last 7 days, This month, Last month, This quarter, This year) or any range on the calendar, applied with Apply; on a phone it opens as a bottom sheet. The compare switch turns the dashed previous period in the revenue chart, the KPI deltas and the salespeople's changes on and off. Refresh spins its icon, shows a thin bar under the header and announces "Updated at 16:40" when it is done; a refreshShortcut binds it to a key. Export opens a menu with CSV and PDF. Hover, tap or scrub a chart to read a point, or focus it and use the arrow keys; legend rows toggle a series, a donut slice or a legend row selects a source (Escape clears). KPI tiles with a link lift on hover and open on Enter. Widgets with a menu have a ⋯ button labelled with their title, and View all links end their headers. Below 672px the widgets stack in one column, the KPIs go two per row (an odd last tile takes the full row), the latest invoices turn from a table into cards, and refresh and export fold into one ⋯ that opens as an action sheet. Values count up and charts draw in once; a new period swaps the numbers with a short fade instead of counting again, and reduced motion drops the counts and the chart growth.

KPI Dashboard is a Pro React block in the React blocks category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add kpi-dashboard, together with Badge, Bar chart, Button, Date Range Picker, Donut chart and 23 more and the npm packages motion 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 kpi-dashboard

KPI Dashboard 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 KpiDashboard from @/components/ui/kpi-dashboard.

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")}
      onExport={(format) => window.open(`/api/dashboard/export?format=${format}`)}
      widgetLinks={{ invoices: "/invoices", activity: "/activity" }}
      locale="en-US"
      labels={{ invoices: "Recent invoices", viewAll: "See all" }}
    />
  )
}

Props

  • datadefault "KPI_DASHBOARD_SAMPLE"
    KpiDashboardData

    { kpis: KpiItem[]; revenue?: { date, value, previous? }[]; revenueBy?: { label, value }[]; sources?: { label, value, color? }[]; funnel?: { label, value }[]; leaderboard?: { id, name, avatar?, value, change? }[]; invoices?: { id, number, client, amount, issuedAt, dueAt, status, href? }[]; activity?: TimelineItemData[] }. KpiItem: { id, label, value (null shows a dash), format?, delta?, deltaFormat? (percent, number, currency, points), invertTrend?, sparkline?, sparklineType?, icon?, iconTone? (the icon tile tint), href?, goal?, info? }. A widget whose data is missing is hidden; an empty array shows its empty state. The sample (Northwind CRM, September 2026) is pinned to 2026-09-24 4:40 PM.

  • titledefault "Dashboard"
    ReactNode

    The page's one <h1>: 22px on phones, 24px from 768px.

  • descriptiondefault ""
    ReactNode

    A line under the title (the company, the team, a greeting).

  • revenueByLabeldefault ""
    string

    The bar chart's title. Empty uses labels.revenueBy ("Revenue by month"); set "Revenue by city" when the bars are cities.

  • layoutdefault "comfortable"
    "comfortable" | "compact"

    comfortable: 24px gaps (16px on phones), md KPI cards and 280px charts. compact: 16px gaps, one joined panel of sm KPI tiles and 240px charts.

  • kpiColumnsdefault 0
    number

    KPI tiles per row on wide dashboards (at most 2 on phones and 3 from 576px). 0 is auto: 2, then 3, then every tile in one row (up to 5) from 1024px. The last row always fills its width.

  • revenueByOrientationdefault "vertical"
    "vertical" | "horizontal"

    vertical columns with the latest one highlighted (months), or horizontal bars for long labels (cities, products).

  • activityLimitdefault 5
    number

    Activity items shown before View all. 0 shows every item.

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

    The surface of every widget and KPI tile: raised panels (surface), flat gray tiles (card) or hairline outlines on the page (outline).

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

    Corners of the widgets and KPI tiles: 0, 12, 16 or 24px.

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

    The accent of the single-series marks: the revenue line and its area, the bars, the funnel, the salespeople's bars, the KPI icons and sparklines. Status colors (paid, overdue, deltas) keep their meaning.

  • colordefault ""
    string

    Any CSS color for the same marks; replaces the tone.

  • currencydefault "USD"
    string

    ISO 4217 code for every amount. The block formats, it never converts: USD data with EUR prints euros.

  • localedefault ""
    string

    Intl locale for numbers, money and dates (en-US: $248,300, 10/24/2026). Empty uses the UIProvider's, then the browser's.

  • defaultComparePreviousdefault true
    boolean

    Starts with the previous period on: the dashed series, the KPI deltas and the salespeople's changes. comparePrevious / onComparePreviousChange control it.

  • loadingdefault false
    boolean

    Every KPI tile and widget in its skeleton (charts, rows, table, feed); the period picker and the switch stay usable.

  • animateddefault true
    boolean

    Values count up and charts draw in once, on first view. Reduced motion turns both off on its own.

  • animateIndefault false
    boolean

    Widgets stagger in (40ms apart) when the dashboard mounts on the client. Server HTML and hydration show the final state.

  • refreshShortcutdefault ""
    string

    A key combo for refresh ("shift+r", "mod+shift+r"), shown in the button's tooltip and the ⋯ menu. Empty binds nothing.

  • period
    DateRange

    The period on screen, { from, to } (controlled). The data is the app's job: the block shows the period and hands changes back.

  • defaultPeriod
    DateRange

    The starting period while uncontrolled. Default: this month of now.

  • onPeriodChange
    (period: DateRange) => void

    A preset or a range was applied.

  • comparePrevious
    boolean

    Controlled compare switch.

  • onComparePreviousChange
    (compare: boolean) => void

    The compare switch changed.

  • widgets
    ("revenue" | "revenueBy" | "sources" | "funnel" | "leaderboard" | "invoices" | "activity")[]

    Which widgets show and in what order (DOM order is the phone order). Default: all, in this order, each hidden when its data is missing; a widget listed here shows its empty state instead.

  • onRefresh
    () => (void | Promise<unknown>)

    Shows the refresh button. A returned promise spins it, shows a bar under the header and announces the time when it settles (or labels.refreshError when it rejects).

  • onExport
    (format: "csv" | "pdf") => void

    Shows the Export menu. Exporting is the app's job (the block does not import spreadsheet).

  • widgetErrors
    Partial<Record<KpiDashboardWidgetId, ReactNode>>

    An error state with a Try again button in place of a widget's content (true uses the default message).

  • onWidgetRetry
    (widget: KpiDashboardWidgetId) => (void | Promise<unknown>)

    Try again in a widget's error state; a returned promise spins the button.

  • widgetLinks
    Partial<Record<KpiDashboardWidgetId, string>>

    A View all link at the end of a widget's header (the invoices page, the activity log).

  • widgetDescriptions
    Partial<Record<KpiDashboardWidgetId, ReactNode>>

    A line under a widget's title.

  • widgetActions
    (widget: KpiDashboardWidgetId) => (MenuEntry[] | undefined)

    Menu entries for a widget's ⋯ button, labelled "More actions for <title>"; a bottom sheet on phones.

  • linkComponent
    (props: LinkComponentProps) => ReactNode

    Renders View all links and invoice links through your router (default: the LinkProvider's, else <a>).

  • now
    Date | number

    The moment the default period and the relative activity times are measured from. Default: the current time (useNow), or 24.09.2026 16:40 for the sample.

  • labels
    Partial<KpiDashboardLabels>

    Every string: period, presets { today, last7Days, thisMonth, lastMonth, thisQuarter, thisYear }, compare, refresh, refreshing, updated(time), refreshError, export, exportCsv, exportPdf, more, moreFor(title), kpis, vsPrevious, points(value), revenue, current, previous, revenueBy, sources, total, funnel, leaderboard, invoices, activity, viewAll, statuses { paid, unpaid, overdue, draft }, number, client, status, amount, due, empty, error, retry. English defaults in DEFAULT_KPI_DASHBOARD_LABELS.

  • classNames
    Partial<Record<"content" | "header" | "title" | "description" | "status" | "controls" | "period" | "compare" | "actions" | "progress" | "kpis" | "kpi" | "grid" | "widget" | KpiDashboardWidgetId, string>>

    Extra classes for the parts. Each widget's content wrapper takes its id's key.

Parts

  • DashboardWidget

    component

    The frame of every tile, for your own dashboards: a <section aria-labelledby> with an h2 (headingLevel), title, description, icon, iconTone (the icon tile's tint, default neutral), actions (MenuEntry[] for a ⋯ menu, or a node), href + linkLabel (View all), span 1 | 2 | 3 | "full" (columns of the 3 column grid; phones take the full width), rowSpan, padding "none" | "md" (charts and tables bleed), variant, radius, loading + skeleton ("chart" | "list" | "table" | "stat"), empty / emptyState, error / onRetry, footer, labels. Reads useDefaults("dashboard-widget"). Props: DashboardWidgetProps.

  • KpiDashboardGrid

    component

    The widget grid: one column, two from 672px and three from 1152px of its own width (container queries, so a sidebar never squeezes it). layout sets the gaps; fill (default true) stretches the last widget of a short row so no layout leaves a hole; animateIn staggers the widgets in on a client mount. Props: KpiDashboardGridProps.

  • KPI_DASHBOARD_SAMPLE

    constant

    Northwind CRM, September 2026 against August: $248,300 revenue (+12.4%), 64 invoices, $31,650 outstanding, 9 new clients, 23% conversion, the pipeline funnel, five salespeople, the latest invoices and eight events. Type: KpiDashboardData.

  • DEFAULT_KPI_DASHBOARD_LABELS

    constant

    The English strings, to spread under a translation. Type: KpiDashboardLabels.

  • DEFAULT_DASHBOARD_WIDGET_LABELS

    constant

    The English strings of DashboardWidget: the actions menu name, View all, the empty and error messages and Try again.

Examples

Open

Compact: KPIs and invoices

layout="compact" joins the KPI tiles into one panel; widgets={["invoices"]} keeps a single widget.

Open

Loading

Every tile and widget in its own skeleton while the period stays usable. Press the button to swap.

Open

An empty period

A month that has not started: dashes in the tiles and an empty state in every widget.

Open

A widget error with retry

widgetErrors puts one widget in its error state; Try again resolves after a fake request.

Open

Your own dashboard from the parts

KpiDashboardGrid and DashboardWidget around a tracker, a gauge and a leaderboard for the support team.

Open

On a phone (390px)

One column, KPIs two by two, invoices as cards, refresh and export in one ⋯ action sheet.

Open

Brand tone, flat tiles, cities

tone="brand", variant="card", radius="lg" and horizontal bars for revenue by city.

Dependencies

motionlucide-reactbadgebar-chartbuttondate-range-pickerdonut-chartdropdown-menuempty-statefunnel-chartleaderboardline-chartprogressskeletonstat-cardswitchtabletimelinetooltipuse-controllable-stateuse-localeuse-shortcutcommercedate-utilsmotionnavigationnumber-formatrelative-timetonesui-config

Files

  • components/ui/kpi-dashboard.tsx

FAQ

Is the React KPI Dashboard block free?

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

How do I install KPI Dashboard?

Run npx wingo-ui@latest add kpi-dashboard. It also adds the Wingo UI items it builds on (Badge, Bar chart, Button, Date Range Picker, Donut chart and 23 more) and the npm packages motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/kpi-dashboard.

How does KPI Dashboard behave on phones?

Pick the period in the header: presets (Today, Last 7 days, This month, Last month, This quarter, This year) or any range on the calendar, applied with Apply; on a phone it opens as a bottom sheet.

See also

Stat CardLine chartLeaderboardTimelineTableAdmin Dashboard Page

More React blocks

Listing Detail PageMarketplace Search PageMarketplace ShellOrder History PagePost Listing WizardPricing Page
All 26 React blocks

Articles using this component

  • Shadcn KPI Cards: Deltas, Sparklines and Goal Bars

    Component guides · Nov 6, 2026 · 9 min read

  • Nextjs Admin Dashboard Tutorial: 5 Screens, Phone-Ready

    Build it: screens and apps · Oct 9, 2026 · 23 min read

PreviousInvoice PageNext Listing Detail Page