M
MetricUI
Cards

KpiCard

A metric card showing a single KPI with optional comparison, sparkline, goal progress, and conditional formatting.

import { KpiCard } from "metricui";

Use KpiCard when you need to display a single key metric prominently. It's the core building block of MetricUI dashboards — combining value display, trend comparison, sparklines, goal tracking, and conditional formatting in one component. For showing multiple metrics in a dense row, use StatGroup instead.

Basic Example

Revenue
$0
<KpiCard
  title="Revenue"
  value={142300}
  format="currency"
  icon={<DollarSign />}
/>

Comparisons & Sparklines

Pass a comparison object to show period-over-period change. Add a sparkline for inline trend visualization. You can pass an array of comparisons to show multiple periods.

Monthly Revenue
$0
+10.7%vs last month
<KpiCard
  title="Monthly Revenue"
  value={142300}
  format="currency"
  comparison={{ value: 128500 }}
  comparisonLabel="vs last month"
  sparkline={{
    data: [98, 105, 110, 108, 120, 135, 142],
    type: "line",
    interactive: true,
  }}
/>

Goal Progress

The goal prop adds a progress bar showing progress toward a target. Configure labels, colors, and what info to display.

Conversion Rate
0%
Q1 Target(5%)1% left
<KpiCard
  title="Conversion Rate"
  value={4.2}
  format="percent"
  goal={{
    value: 5,
    label: "Q1 Target",
    showProgress: true,
    showTarget: true,
    showRemaining: true,
  }}
/>

Conditional Formatting

Use conditions to color the value based on thresholds. Rules are evaluated top-to-bottom — first match wins. Supports simple operators (above, below, between) and compound rules (and/or).

Server Uptime
0%
<KpiCard
  title="Server Uptime"
  value={99.7}
  format="percent"
  conditions={[
    { when: "above", value: 99.5, color: "emerald" },
    { when: "between", min: 99, max: 99.5, color: "amber" },
    { when: "below", value: 99, color: "red" },
  ]}
/>

Data States

Every component handles loading, empty, error, and stale states. Pass individual props or use the grouped state prop.

Loading

Error

Failed to load data

// Loading state
<KpiCard title="Revenue" value={0} loading />

// Error state
<KpiCard title="Revenue" value={0} error={{ message: "Failed to load" }} />

// Grouped state prop
<KpiCard title="Revenue" value={0} state={{ loading: isLoading, error }} />

Props

PropTypeDescription
title*
DynamicString

Card title. Can be a static string or a template function receiving MetricContext.

value*
number | string | null | undefined

The metric value. Pass a number for formatted display. Pass a string (e.g. '6:42 AM', 'Operational') for non-numeric KPIs — displayed as-is, no formatting applied. Pass null or undefined for null-state display.

format
FormatOption

How to format numeric values. Shorthand: 'currency', 'percent', 'compact', 'number', 'duration'. Or FormatConfig object: { style, suffix?, prefix?, precision?, compact?, currency?, locale? }. For custom units: { style: 'number', suffix: '°F', compact: false }. Ignored when value is a string.

comparison
ComparisonConfig | ComparisonConfig[]

Single comparison or array of comparisons. Each comparison computes change from a previous value and shows a trend badge.

goal
GoalConfig

Goal/target configuration. Shows a progress bar below the value.

conditions
Condition[]

Conditional formatting rules. First matching condition colors the value text.

sparkline
{ data: number[]; previousPeriod?: number[]; type?: SparklineType; interactive?: boolean }

Sparkline configuration object — alternative to individual sparkline* props.

icon
React.ReactNode

Icon rendered next to the title.

description
DynamicReactNode

Description content shown in a popover next to the title.

subtitle
DynamicString

Subtitle shown below the title.

footnote
DynamicString

Small footnote at the bottom of the card.

comparisonLabel
DynamicString

Label shown next to the primary comparison badge (e.g. 'vs last month').

tooltip
TooltipConfig

Custom tooltip configuration for the value.

onClick
() => void

Click handler for the entire card.

href
string

Turns the card into a link (<a> tag).

drillDown
boolean | DrillDownConfig | ((event: { value: number | string; title: string }) => React.ReactNode)

Enable drill-down on card click. `true` auto-generates a detail panel. Pass a DrillDownConfig for the legacy hover-corner link. Pass a render function for full control over the panel content. Requires DrillDown.Root wrapper for boolean/function modes.

drillDownMode
DrillDownMode

Presentation mode for the drill-down panel. "slide-over" (default) slides from the right, full height. "modal" renders centered and compact.

copyable
boolean

Show a copy button that copies the raw formatted value to clipboard.

animate
boolean | AnimationConfig

Enable count-up animation. `true` enables default, or pass AnimationConfig for fine control. Falls back to config.animate.

highlight
boolean | string

Attention ring around the card. `true` uses accent color, or pass a CSS color string.

trendIcon
TrendIconConfig

Custom trend icons for comparison badges (up/down/neutral).

nullDisplay
NullDisplay

What to show when value is null/undefined/NaN/Infinity. Falls back to config.nullDisplay.

titlePosition
TitlePosition

Where the title appears relative to the value: 'top', 'bottom', or 'hidden'.

titleAlign
TitleAlign

Horizontal alignment for card content: 'left', 'center', or 'right'.

loading
boolean

Show a skeleton placeholder.

empty
EmptyState

Empty state configuration with message, icon, and action.

error
ErrorState

Error state configuration with message and retry callback.

stale
StaleState

Stale data indicator shown in the top-right corner.

state
{ loading?: boolean; empty?: EmptyState; error?: ErrorState; stale?: StaleState }

Grouped data state configuration — alternative to individual loading/empty/error/stale props.

variant
CardVariant

Visual variant: 'default', 'outlined', 'ghost', 'elevated', or any custom string. CSS-variable-driven via [data-variant]. Falls back to config.variant.

dense
boolean

Compact/dense layout with reduced padding. Falls back to config.dense.

accent
string

Override border color with a custom accent CSS color.

className
string

Additional CSS class names for the root element.

classNames
{ root?: string; title?: string; value?: string; comparison?: string; sparkline?: string; goal?: string; footnote?: string }

Sub-element class name overrides for granular styling.

id
string

HTML id attribute.

data-testid
string

Test id for testing frameworks.

onCopy
(value: string) => void

Callback when value is copied (requires copyable).

exportable
ExportableConfig

Enable PNG/CSV/clipboard export via the ExportButton dropdown. true: exports single value as CSV. { data: rows[] }: exports provided rows as CSV. Set globally on MetricProvider or per-component. Charts auto-export their source data.

Data Shape

// KpiCard accepts a numeric or string value
interface KpiCardData {
  value: number | string | null | undefined;
  // Optional comparison previous value(s)
  comparison?: { value: number; label?: string };
  // Optional sparkline data
  sparklineData?: number[];
}

Notes

  • Uses forwardRef — you can pass a ref to the root div.
  • When `href` is provided, the root element becomes an <a> tag.
  • The `sparkline` object prop takes precedence over individual sparklineData/sparklineType/etc props.
  • The `state` object prop takes precedence over individual loading/empty/error/stale props.
  • Dynamic strings (DynamicString) can be a plain string or a function receiving MetricContext.
  • Condition colors can be named tokens ('emerald', 'red', 'amber', etc.) or raw CSS colors ('#ff6b6b', 'rgb(...)').
  • KpiCard uses CardShell internally. All CardShell features (export, auto empty state, variant, dense, data states) work automatically.
  • exportable: true exports the single KPI value as CSV. Override with exportable={{ data: detailRows }} to export a detail table (e.g., breakdown by region).
  • Auto empty state: when exportData is empty, CardShell shows 'Nothing to show — try adjusting your filters' automatically. Override globally via MetricProvider.emptyState or per-component via the empty prop.

Playground

Experiment with every prop interactively. Adjust the controls on the right to see the component update in real time.

Live Preview

Revenue
$0
+12.5%vs last month

Code

<KpiCard
  title="Revenue"
  value={78400}
  format="currency"
  icon={<DollarSign />}
  comparison={{ value: 69700 }}
  comparisonLabel="vs last month"
  sparklineData={[42, 45, 48, 51, 49, 55, 58, 62, 59, 65, 71, 78]}
  animate={{ countUp: true }}
/>

Props

Adjust props to see the card update in real time

Where the title sits relative to the value

Horizontal alignment for title, value, and comparisons

Auto picks K/M/B/T by magnitude, or lock to a scale

Decimal places

Accepts any ReactNode — Lucide, Heroicons, SVGs, etc.

Previous period value — change is computed

Accepts any ReactNode — these are presets

Line chart or bar chart sparkline