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
<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.
<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.
<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).
<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
| Prop | Type | Default | Description |
|---|---|---|---|
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 | "slide-over" | 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 | "dash" | What to show when value is null/undefined/NaN/Infinity. Falls back to config.nullDisplay. |
titlePosition | TitlePosition | "top" | Where the title appears relative to the value: 'top', 'bottom', or 'hidden'. |
titleAlign | TitleAlign | "left" | 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 | false | 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.
- The
aiContextprop (inherited from BaseComponentProps) adds business context for AI Insights analysis. See the AI Insights guide for details.
Playground
Experiment with every prop interactively. Adjust the controls on the right to see the component update in real time.
Live Preview
Code
<KpiCard
title="Revenue"
value={78400}
format="currency"
icon={<DollarSign />}
comparison={{ value: 69700 }}
comparisonLabel="vs last month"
sparkline={{ data: [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