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.
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"
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