KPI Card
A compact metric card for business or finance numbers, optionally per period.
≈ 2,332 tokens per use (tool 1,343 · guidance 367 · typical payload 622)How token costs are estimated
Example: KPI Card
How are sales tracking?
Sales targets
| Part | Value | Share |
|---|---|---|
| Spend | $4,820.12 | 43% |
| Leads | $4,103.50 | 37% |
| Return rate | $2,309.86 | 21% |
- Spend$4,820.12Up 4.2% vs last week
- Leads$4,103.50Up 12.5% vs last week
- Return rate$2,309.86Down 1.8% vs last week
When to use
- The user asks how a business or finance number is tracking, from a connected tool: sales this week, what's left on a loan, where transactions went this month, revenue this year.
- One headline figure with a small breakdown answers it at a glance.
When not to use
- The user wants to explore a series or compare several over time. Use a Chart.
- There are many records to read. Use a Data Table.
- It's one personal insight in a sentence. Use an Insight Card.
- The numbers aren't from the user's data. Don't invent them.
How the agent should use it
- Every number must come from the tool result. Totals and shares computed from it are fine; estimates are not.
- Pick the visual for the question:
segmentsfor a split of a total,dotsfor installments or quotas (N of M),treemapfor shares of spend by category,barsfor a value per period with one highlighted. - Only add
periodswhen you have data for each. Key every block indataby its period key, or put a single block underdefault. - Keep segments and legend rows in the same order, at most 5. Treemap tiles: at most 10, with shares summing to about 100.
- Deltas are in percent units (
2.6is +2.6%) with a shortdeltaLabel, e.g. "vs last week". The label is read by screen readers; visually the period switch already gives the context. - Add the
actiononly when the host can open the source data.
Visuals
type | Shows | Notes |
|---|---|---|
segments | A stacked bar under the headline | Widths are proportional to value, with small gaps. The legend is items when given (same order), otherwise the parts |
dots | Rows of a label and a big value, with N of M dots | Up to 10 dots per row; filled dots are solid, the rest are outlined. "3 of 6" is in the hidden data table |
treemap | Squarified tiles, area proportional to share | Each tile has its label, share and delta when there's room. Tiles that would be smaller than 56 × 56 px at the card's width are folded into "Other" |
bars | Rounded bars with a y axis | highlight emphasizes one bar; bars from forecastFrom on are hatched, with a one-item "Forecast" key |
Periods
With periods, the header shows a segmented switch (one pressed button at a time). Switching re-renders the block for that period: numbers roll to their new values, segment and bar sizes animate, and the treemap re-lays out. The switch is local to the card and never sent to the agent, but the current period is included when the user presses the header action.
Interaction
The header's arrow button sends a result each time it's pressed. The card never locks: it's a view of the data with a way into the source. kind is a closed set, so the agent can't wire arbitrary behavior; the host maps it to a screen.
Locales
Numbers, currency and percentages are formatted with Intl in locale (default en-US). With de-DE, for example, values read "15.243,23 $". Values roll digit by digit only when the roller can reproduce Intl's text exactly; otherwise they change without the animation. The card's built-in words ("Forecast", "Other") are in English.
Accessibility
Each visual is a figure with a summary (aria-label) and a visually hidden table of its data; the drawn shapes are hidden from screen readers. Rolling numbers keep the real value in a visually hidden span. Deltas show a sign, an arrow and text, and are read as "Up 2.6% vs last week"; color is never the only cue. Treemap text color is picked per fill for at least 4.5:1 contrast in both themes. The period buttons use aria-pressed inside a labelled group, and the footer is a polite live region, so the new total is announced after a switch.
Installation
npx shadcn@latest add https://your-site.com/r/kpi-card.json
Usage
import { tool } from "ai"
import { kpiCardTool } from "@/components/genui/kpi-card.schema"
const tools = {
[kpiCardTool.name]: tool({
description: kpiCardTool.description,
inputSchema: kpiCardTool.inputSchema,
}),
}When the user presses the header's arrow button, the agent (or your host) receives:
{ action: "details", period: "weekly" } // or "open"; period only with periodsWithout an action the card is display-only, so give the tool an execute that returns immediately. Render it directly, mapping results to your app's screens:
<KpiCard
{...part.input}
status={part.state === "input-streaming" ? "streaming" : "ready"}
onResult={(r) => openScreen(r.action, r.period)}
/>With LangGraph
Push it from a graph node with ui.push({ name: "show_kpis", props: { toolCallId, ...args } }, { message }), and render the thread's UI messages with the Renderer.
Restoring from history
There's no finished state to restore. Render the stored tool input again; the card stays live and opens on defaultPeriod.
Schema
| Prop | Type | Notes |
|---|---|---|
title | string | Up to 60 characters |
periods | { key, label }[]? | 2–4. Adds the period switch |
defaultPeriod | string? | Defaults to the first period |
data | Record<periodKey | "default", Block> | Data-bound |
action | { label, kind }? | kind: open or details. Adds the header's arrow button; label is its tooltip |
locale | string? | BCP 47, default en-US |
| Block field | Type | Notes |
|---|---|---|
headline | { label?, value, format, currency? }? | The big number |
visual | one of the visuals below | |
items | { label, value, format, currency?, delta?, deltaLabel? }[]? | Up to 8 legend or metric rows. deltaLabel is for screen readers only |
footer | { label, value, format, currency? }? | e.g. "Total earned" |
| Visual | Fields |
|---|---|
segments | parts: { label, value }[] (1–5), format?, currency? (default: the headline's) |
dots | rows: { label, value, format, currency?, filled, total }[] (1–6; total ≤ 10) |
treemap | tiles: { label, share, delta? }[] (1–10; share 0–100) |
bars | bars: { label, value }[] (2–24), highlight?, forecastFrom?, format?, currency? |
format is currency (with an ISO 4217 currency), number or percent. Percent values and deltas are in percent units: 12.5 is 12.5%.
Host props: status (streaming or ready), onResult and className.