KPI Board
The top of a dashboard – three trend cards, an overview chart that compares this period with the last, and a focus card; hover shows any day's value and every number rolls when the range changes.
Part of affectUI Pro. Install: npx shadcn@latest add https://www.affectui.com/r/kpi-board.json · Import from @/components/ui/kpi-board
When to use
- The top of a dashboard: a few headline numbers with their trend, one chart that compares this period with the last, and one product or plan in focus
- Sales, growth and usage overviews of a SaaS or AI product, above a table or a list
- When the charts belong together – same range, same filter – and should react as one
When not to use
- One metric in depth – use KPI Chart or Breakdown Chart
- Parts of a whole (shares, funnels, rankings) – the board only draws trends over time
- Reports that are printed or exported – this is built for pointing and switching
Usage
import { KpiBoard, type KpiBoardData } from "@/components/ui/kpi-board";
const ranges = [
{ id: "7d", label: "Last 7 days" },
{ id: "30d", label: "Last 30 days" },
];
export function Overview({ data }: { data: Record<string, KpiBoardData> }) {
// data["30d"] = { stats, overview, focus }
return <KpiBoard data={data} ranges={ranges} defaultRange="30d" compareLabel="vs. last month" />;
}Props
| Name | Type | Default | Description |
|---|---|---|---|
data | Record<string, KpiBoardData> | One data set per range id: { stats, overview, focus } | |
ranges | { id: string; label: string }[] | Options of the date range picker | |
range | string | Controlled range id | |
defaultRange | string | first range | Starting range when uncontrolled |
onRangeChange | (id: string) => void | Called with the new range | |
segments | { id: string; label: string }[] | A filter inside the overview card, e.g. All items / Pro / Team – it only switches that card's lines; no filter when left out | |
segment | string | Controlled segment id | |
defaultSegment | string | first segment | Starting segment when uncontrolled |
onSegmentChange | (id: string) => void | Called with the new segment | |
compareLabel | string | "vs. last period" | Words after every change |
currentLabel | string | "This period" | Name of the main line in the tooltip and the focus card |
previousLabel | string | "Last period" | Name of the comparison line |
formatMoney | (value: number) => string | $1,234.00 | Formats money values |
icon | ReactNode | Icon in the focus card | |
title | string | Heading on the left of the bar above the cards | |
setup | { label: string; onClick: () => void } | The small button at the top right of the focus card, e.g. Set up | |
action | { label: string; onClick: () => void } | The button at the bottom of the focus card | |
className | string | Extra class on the root |
Keyboard
- Tab: Through the pickers and the action
- Enter / Space / ↓: On a picker: opens its menu
- ↑ / ↓ / Home / End: In an open menu: moves through the choices
- Enter: Picks the highlighted choice
- Esc: Closes the menu without changing anything
Accessibility
- Every card is a labelled region with its numbers as text; the charts themselves are decorative
- The pickers are comboboxes with a listbox: the open state, the highlighted and the chosen option are announced
- Changes carry an arrow or a sign next to the color, and the color follows whether the change is good news (lowerIsBetter)
Responsive
- The date range sits above the cards on the right. Three small cards over a wide overview and the focus card; below 820px of container width the two lower cards take a full row each, below 600px everything stacks
- It follows its container, not the window
- Dark mode built in: switches when an ancestor (usually <html>) has the class dark or data-theme="dark"
Requirements
- React 19
Related
- Metrics Dashboard – Sales overview – KPI cards pick the metric, one chart switches between dotted line and bars, with top products and channels
- KPI Chart – Metric card with a multi-line chart and a tooltip that follows the pointer
- Usage Meter – Plan usage and AI credits – a stacked credits meter by category, daily sparkline, limits and a button that speaks up near the limit