Insight card
A generated insight with a metric, a small chart and its sources.
Texting the remittance link shortens calls
Based on 1,422 remittance calls this week
Sources2
import { InsightCard } from "@oration/canon/components/ai/insight-card";import { Button } from "@oration/canon/components/button";import { MiniChart } from "@oration/canon/components/mini-chart";import { toast } from "@oration/canon/components/toast";export function Hero() { return ( <InsightCard className="w-full max-w-md text-left" title="Texting the remittance link shortens calls" meta="Based on 1,422 remittance calls this week" metric={{ label: "shorter average call", value: "41 s", delta: -0.18, goodDirection: "down", }} chart={ <MiniChart data={[ 212, 208, 204, 199, 187, 176, 171, 168, 166, 162, 159, 158, 156, 154, ]} type="area" width={320} height={40} ariaLabel="Average remittance call length, 14-day trend" className="h-10 w-full" /> } summary="Calls where Nora offered to text the remittance advice ended 41 seconds sooner and needed fewer repeats of the ACH trace number. The payment status line doesn't offer the text yet." sources={[ { id: "s1", title: "Call with Northwind Freight", snippet: "Can you just text me that number?", kind: "transcript", meta: "Call on Sep 24", }, { id: "s2", title: "Remittance advice FAQ", snippet: "Suppliers can get the remittance by email or text.", kind: "doc", }, ]} actions={ <> <Button type="button" variant="outline" size="sm" onClick={() => toast.add({ title: "Coaching drafted for Nora" }) } > Create coaching </Button> <Button type="button" variant="ghost" size="sm" onClick={() => toast.add({ title: "Opened 1,422 remittance calls", }) } > Compare calls </Button> </> } /> );}Usage#
Insight card reports one finding the model surfaced from Cedarline's data: a title that states the finding, what it's based on, an optional metric in a row with its change, an optional small chart, the explanation, its sources and what to do next. It fills the Agents home What changed this week, ticket report insights, campaign rails and Copilot answers. The metric is a 20px figure beside its label, not a hero number, so a row of insight cards never turns into a metric mosaic. The common mistake is titling it with a metric name instead of the finding.
When to use
- For a weekly finding on a home or overview page: Texting the remittance link shortens calls.
- Beside an analytics chart, to say in words what moved and why.
- Inside a Copilot answer to an analysis question, when there's one metric worth naming.
- When the finding needs its evidence: calls, records or documents it came from.
When not to use
- For a next step with a confidence level. Use Recommendation card
- For a row of headline metrics at the top of a page. Use Inline stat strip
- For one metric with its label and change and no explanation. Use Stat strip
- For a trend that deserves axes, a legend and a tooltip. Use Chart
- For a generated summary of one record or conversation. Use Streaming text
No hero metrics
The Tabular Figures Rule
The Ink Fill Rule
MiniChart in its default tone. Only the delta turns semantic, because better or worse is a verdict.The Quiet Indigo Rule
Anatomy#
Transfers to AP rose on the disputes line
Based on 318 calls this week
Sources1
- Mark. A 16px Slate Meta icon, sparkles by default. Decorative.
- Title. An
h3at 14px, weight 600: the finding as a sentence. - Meta. Optional 12px Slate Meta: what it's based on.
- Metric. Optional: the value at 20px, weight 600, tabular; its label in 13px Slate Meta; then the delta.
- Delta. 12px medium tabular with an up-right or down-right arrow. Ledger Green when the direction is good, Signal Red when it isn't.
- Chart. Optional, full width. Usually a 40px area
MiniChart. - Summary. 13px ink at 85%: what happened and why.
- Sources. Optional numbered source cards in a sideways-scrolling row.
- Actions. Buttons in a wrapping row, usually outline.
Examples#
Deltas and good direction
A string delta shows as written; a number is a fraction shown as a percentage. goodDirection decides the color, so a rising transfer rate is red.
Spanish line containment rose after the numbers coaching
Since Sep 2, 894 calls
Halcyon calls are transferring more often
Based on 46 calls this month
import { InsightCard } from "@oration/canon/components/ai/insight-card";import { PhoneForwardedIcon } from "lucide-react";export function Deltas() { return ( <div className="grid w-full max-w-3xl gap-4 text-left md:grid-cols-2"> <InsightCard title="Spanish line containment rose after the numbers coaching" meta="Since Sep 2, 894 calls" metric={{ label: "containment", value: "74%", delta: "+6 pts", goodDirection: "up", }} summary="Reading invoice IDs one character at a time cut repeat requests by half." /> <InsightCard icon={<PhoneForwardedIcon />} title="Halcyon calls are transferring more often" meta="Based on 46 calls this month" metric={{ label: "transfer rate", value: "31%", delta: 0.12, goodDirection: "down", }} summary="Most are about the early-payment discount, which Nora can't explain from the remittance alone." /> </div> );}Without a metric
Some findings are a sentence and an action. Leave out the metric and the chart rather than inventing a number.
Three suppliers account for half of this week's payment calls
From 214 supplier calls since Monday
import { InsightCard } from "@oration/canon/components/ai/insight-card";import { Button } from "@oration/canon/components/button";import { toast } from "@oration/canon/components/toast";export function TextOnly() { return ( <InsightCard className="w-full max-w-md text-left" title="Three suppliers account for half of this week's payment calls" meta="From 214 supplier calls since Monday" summary="Northwind Freight, Orchard Street and Halcyon called 107 times between them. Each has an invoice in Tuesday's ACH run, and most asked when it will land." actions={ <Button type="button" variant="outline" size="sm" onClick={() => toast.add({ title: "Remittance emails drafted for 3 suppliers", }) } > Email their remittances early </Button> } /> );}On a home page
Two up under a Title-sized heading, each with a 14-day ink area chart and one outline action.
What changed this week
Texting the remittance link shortens calls
Based on 1,422 remittance calls this week
Missing W-9s are holding more invoices
Friday's payment run, 212 invoices
import { InsightCard } from "@oration/canon/components/ai/insight-card";import { Button } from "@oration/canon/components/button";import { MiniChart } from "@oration/canon/components/mini-chart";import { toast } from "@oration/canon/components/toast";export function HomeSection() { const insights = [ { id: "ins_sms", title: "Texting the remittance link shortens calls", meta: "Based on 1,422 remittance calls this week", metric: { label: "shorter average call", value: "41 s", delta: -0.18, goodDirection: "down" as const, }, trend: [ 212, 208, 204, 199, 187, 176, 171, 168, 166, 162, 159, 158, 156, 154, ], summary: "Calls ended sooner when Nora offered to text the remittance advice.", action: "Create coaching", }, { id: "ins_w9", title: "Missing W-9s are holding more invoices", meta: "Friday's payment run, 212 invoices", metric: { label: "invoices on hold", value: "17", delta: "+9", goodDirection: "down" as const, }, trend: [6, 7, 7, 8, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], summary: "Eleven of the holds are suppliers onboarded since September 1.", action: "Request W-9s", }, ]; return ( <section aria-labelledby="insights-demo-heading" className="w-full max-w-3xl text-left" > <h2 id="insights-demo-heading" className="mb-3 text-sm font-semibold" > What changed this week </h2> <div className="grid gap-4 md:grid-cols-2"> {insights.map((item) => ( <InsightCard key={item.id} title={item.title} meta={item.meta} metric={item.metric} summary={item.summary} chart={ <MiniChart data={item.trend} type="area" width={320} height={40} ariaLabel={`${item.title}, 14-day trend`} className="h-10 w-full" /> } actions={ <Button type="button" variant="outline" size="sm" onClick={() => toast.add({ title: item.action }) } > {item.action} </Button> } /> ))} </div> </section> );}States#
| State | Treatment |
|---|---|
| Good change | The delta moves in goodDirection: green, with the arrow in that direction. |
| Bad change | The delta moves against it: red. |
| No metric | Leave out metric and the card is title, summary, sources and actions. |
| Loading, empty, error | Not built in. Pages wrap the cards in DataState with a SkeletonCard per card, an empty state that teaches, and a retry. |
Behavior#
- It's a static
<article>; only actions and linked sources are interactive. - A numeric
deltais a fraction:-0.18renders 18% with a down arrow. A string is shown as written without its sign, such as +6 pts as 6 pts with an up arrow. - Direction comes from the sign.
goodDirection(defaultup) decides the color only; the arrow always shows the direction. chartrenders full width between the metric and the summary. Size the chart to its container, such asclassName="h-10 w-full".sourcesrendersSourceCardsin the row layout.
Do and don't#
Texting the remittance link shortens calls
Average call duration
Spanish line containment rose
Content#
- Title: the finding as a sentence in sentence case, with the cause if known: Spanish line containment rose after the numbers coaching.
- Meta: the base and the window: Based on 1,422 remittance calls this week, Since Sep 2, 894 calls.
- Metric label: lowercase noun phrase that reads after the value: 41 s shorter average call, 74% containment.
- Summary: two sentences at most. What changed, why, and what isn't done yet.
- Actions: verbs that lead somewhere: Create coaching, Compare calls.
Accessibility#
- The card is an
<article>with anh3, reachable by heading. - The delta adds a hidden Up or Down before its figure, so the direction is read out. Whether that's good isn't announced (see known gaps).
- The mark is
aria-hidden. - Give a
MiniChartanariaLabelthat names the series and window, such as Average call length, 14-day trend.
Design tokens#
| Token | Used for |
|---|---|
--card | Card surface |
shadow-border | Hairline lift |
--muted-foreground | Mark, meta, metric label |
--foreground | Title, metric value; summary at 85% |
--success | Delta in the good direction |
--destructive | Delta in the bad direction |
--radius-xl | 12px card corners |
API reference#
InsightCard
One finding. Also exported: the InsightMetric type.
Other props spread onto Nothing. Only the props below are read..
| Prop | Type | Default | Description |
|---|---|---|---|
titleRequired | string | No default | The finding as a sentence. |
summaryRequired | React.ReactNode | No default | What happened and why. |
metric | { label: string; value: React.ReactNode; delta?: number | string; goodDirection?: "up" | "down" } | No default | One metric in a row. A numeric delta is a fraction; goodDirection defaults to up. |
chart | React.ReactNode | No default | Usually a MiniChart. |
sources | Source[] | No default | Rendered as a source card row. See Source cards. |
actions | React.ReactNode | No default | Buttons. |
meta | React.ReactNode | No default | What it's based on. |
icon | React.ReactNode | No default | Replaces the sparkles mark. |
className | string | No default | Merged onto the article. |
Known gaps#
Where the implementation and the system disagree today. Follow the system, not the gap.
Whether a delta is good or bad is shown by color only. The arrow and the hidden Up or Down give the direction, not the judgment.
A string delta loses its sign in the text (+6 pts shows as 6 pts); only the arrow keeps it.
The summary is ink at 85% (text-foreground/85), an alpha that isn't one of the named text tokens.
No loading, empty or error state. Every page wraps the cards in DataState itself.
The title is always an h3.