affectUI › Blocks › Analytics

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

NameTypeDefaultDescription
dataRecord<string, KpiBoardData>One data set per range id: { stats, overview, focus }
ranges{ id: string; label: string }[]Options of the date range picker
rangestringControlled range id
defaultRangestringfirst rangeStarting range when uncontrolled
onRangeChange(id: string) => voidCalled 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
segmentstringControlled segment id
defaultSegmentstringfirst segmentStarting segment when uncontrolled
onSegmentChange(id: string) => voidCalled with the new segment
compareLabelstring"vs. last period"Words after every change
currentLabelstring"This period"Name of the main line in the tooltip and the focus card
previousLabelstring"Last period"Name of the comparison line
formatMoney(value: number) => string$1,234.00Formats money values
iconReactNodeIcon in the focus card
titlestringHeading 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
classNamestringExtra 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