Skip to content

Recommendation card

An AI recommendation with its rationale, confidence and impact.

Status
Beta
Category
AI
Adoption
Not used yet
import { RecommendationCard } from "@oration/canon/components/ai/recommendation-card";
packages/canon/src/components/ai/recommendation-card.tsx

Email the ACH trace and remittance to Northwind Freight

High confidence
86%
Aisha Bello has called three times since Friday about INV-20931 and asked for the trace number each time. PMT-58213 went out by ACH this morning.
Avoidsa fourth call this week

Sources2

  1. 1Payment recordPMT-58213ACH, $20,625.00, sent Monday, Sep 28. Trace 091000019284713.
  2. 2Call on Sep 25Call with Aisha BelloCan you just email me the trace? Our bank keeps asking for it.
import { RecommendationCard } from "@oration/canon/components/ai/recommendation-card";import { Button } from "@oration/canon/components/button";import { toast } from "@oration/canon/components/toast";import { CheckIcon, SparklesIcon } from "lucide-react";import * as React from "react";export function Hero() {    const [state, setState] = React.useState<"idle" | "done" | "dismissed">(        "idle",    );    if (state === "dismissed") {        return (            <div className="flex w-full max-w-md items-center justify-between rounded-xl bg-card px-4 py-3 text-left text-13 text-muted-foreground shadow-border">                Next action dismissed for this conversation.                <Button                    type="button"                    variant="ghost"                    size="xs"                    onClick={() => setState("idle")}                >                    Show again                </Button>            </div>        );    }    return (        <RecommendationCard            className="w-full max-w-md text-left"            icon={<SparklesIcon />}            title="Email the ACH trace and remittance to Northwind Freight"            rationale="Aisha Bello has called three times since Friday about INV-20931 and asked for the trace number each time. PMT-58213 went out by ACH this morning."            confidence={0.86}            impact={{ label: "Avoids", value: "a fourth call this week" }}            sources={[                {                    id: "s1",                    title: "PMT-58213",                    snippet:                        "ACH, $20,625.00, sent Monday, Sep 28. Trace 091000019284713.",                    kind: "record",                    meta: "Payment record",                },                {                    id: "s2",                    title: "Call with Aisha Bello",                    snippet:                        "Can you just email me the trace? Our bank keeps asking for it.",                    kind: "transcript",                    meta: "Call on Sep 25",                },            ]}            actions={                state === "done" ? (                    <span className="flex items-center gap-1.5 text-13 text-success">                        <CheckIcon aria-hidden="true" className="size-3.5" />                        Done                    </span>                ) : (                    <>                        <Button                            type="button"                            variant="outline"                            size="sm"                            onClick={() => {                                setState("done");                                toast.add({                                    type: "success",                                    title: "Trace and remittance sent",                                    description:                                        "Emailed to ap@northwindfreight.example.",                                    actionProps: {                                        children: "Undo",                                        onClick: () => setState("idle"),                                    },                                });                            }}                        >                            Send trace and remittance                        </Button>                        <Button                            type="button"                            variant="ghost"                            size="sm"                            onClick={() => setState("dismissed")}                        >                            Not now                        </Button>                    </>                )            }        />    );}

Usage#

Recommendation card proposes a next step and shows its working: a title that names the step, the rationale in plain words, a ConfidenceMeter (High confidence 86%), the expected impact in a tint well, optional sources and the actions to take it. It powers the Contact Center's next best action and Copilot's suggestions. Unlike an approval card it changes nothing by itself; the person decides whether and how to act. The common mistake is coloring confidence: it is ink, with a word beside it, never green or indigo.

When to use

  • For the next best action in the Contact Center assist panel: Email the ACH trace to Northwind Freight.
  • Inside a Copilot answer, when the answer ends in a suggestion the person can follow up on.
  • When the suggestion is uncertain enough that how sure the model is, and why, should be visible.
  • When there is a concrete impact to weigh, such as a late fee avoided or a repeat call saved.

When not to use

  • For an action Copilot will perform once approved, with fields that change. Use Approval card
  • For a finding with a metric and a trend, such as calls getting shorter. Use Insight card
  • For the sources of an answer on their own. Use Source cards
  • For a measured value such as credits used or talk ratio. Use Meter
  • For a system notice that needs attention, such as an expired W-9. Use Alert

The Ink Fill Rule

Confidence is a magnitude, not a verdict, so its meter is Ink 65 on a Well Gray track. It is never indigo, and never green, amber or red.

The Label-Beside-Color Rule

The meter always travels with a word (High, Medium or Low confidence) and the percentage in tabular figures, so it reads in grayscale.

The Tint Well Rule

The impact sits in a 70% Well Gray well inside the card, and sources are tint-well cards, never nested bordered cards.

The One Filled Button Rule

At most one filled button in actions. In the assist panel and in Copilot, where the view already has a primary action, use outline and ghost.

Anatomy#

Explain the discount to Halcyon

Medium confidence
72%
INV-20877 was paid $760.00 short. Halcyon's terms are 2/10 net 30 and Cedarline paid on day 8.
Settles$760.00 in dispute

Sources1

  1. 1DocumentHalcyon payment terms2% discount when paid within 10 days.
  1. Icon tile. Optional 28px Well Gray tile with a 16px Slate Meta icon.
  2. Title. An h3 at 14px, weight 600: the step, verb first.
  3. Confidence meter. Top right: the band word in 12px Slate Meta, a 48px by 4px ink meter and the percentage in 12px medium tabular figures. Wraps under the title when space is short.
  4. Rationale. 13px Slate Meta: the evidence behind the step.
  5. Impact well. A structured impact puts its label at the left in Slate Meta and its value at the right in medium tabular figures. Any other node renders as is in the well.
  6. Sources. Optional numbered source cards in a sideways-scrolling row.
  7. Actions. Buttons in a wrapping row with 6px gaps.

Examples#

Confidence

High at 80% and above, medium from 55%, low below. The meter stays ink in every band; the word carries the meaning.

High confidence
92%
Medium confidence
68%
Low confidence
41%
import { ConfidenceMeter } from "@oration/canon/components/ai/recommendation-card";export function Confidence() {    return (        <div className="flex flex-col items-start gap-3">            <ConfidenceMeter value={0.92} />            <ConfidenceMeter value={0.68} />            <ConfidenceMeter value={0.41} />        </div>    );}

Impact

Pass { label, value } for the two-column well, or any node when the impact has more than one figure.

Release Orchard Street's held invoice

High confidence
91%
The W-9 arrived this morning and passed TIN matching. OS-4502 can join Friday's payment run.
Pays on time$12,480.00

Move Northwind Freight to a weekly remittance digest

Medium confidence
58%
They asked for 14 remittances by phone in September, all for invoices in the same weekly run.
  • Calls avoidedabout 12 a month
  • Emails sent4 instead of 18
import { RecommendationCard } from "@oration/canon/components/ai/recommendation-card";import { Button } from "@oration/canon/components/button";import { toast } from "@oration/canon/components/toast";export function Impact() {    return (        <div className="grid w-full max-w-3xl gap-4 text-left md:grid-cols-2">            <RecommendationCard                title="Release Orchard Street's held invoice"                rationale="The W-9 arrived this morning and passed TIN matching. OS-4502 can join Friday's payment run."                confidence={0.91}                impact={{ label: "Pays on time", value: "$12,480.00" }}                actions={                    <Button                        type="button"                        variant="outline"                        size="sm"                        onClick={() => toast.add({ title: "Opened OS-4502" })}                    >                        Review invoice                    </Button>                }            />            <RecommendationCard                title="Move Northwind Freight to a weekly remittance digest"                rationale="They asked for 14 remittances by phone in September, all for invoices in the same weekly run."                confidence={0.58}                impact={                    <ul className="flex flex-col gap-1">                        <li className="flex justify-between gap-3">                            <span className="text-muted-foreground">                                Calls avoided                            </span>                            <span className="font-medium tabular-nums">                                about 12 a month                            </span>                        </li>                        <li className="flex justify-between gap-3">                            <span className="text-muted-foreground">                                Emails sent                            </span>                            <span className="font-medium tabular-nums">                                4 instead of 18                            </span>                        </li>                    </ul>                }                actions={                    <Button                        type="button"                        variant="outline"                        size="sm"                        onClick={() =>                            toast.add({                                title: "Digest drafted for Northwind Freight",                            })                        }                    >                        Draft the digest                    </Button>                }            />        </div>    );}

In a Copilot answer

The answer explains, the card proposes, and its one outline action sends the follow-up. Copilot's view already has its filled Send.

Wen Zhou Consulting's W-9 expired in August, and two of their invoices are in Friday's run.

Request a new W-9 from Wen Zhou Consulting

High confidence
81%
Both invoices will be held on Friday without a current W-9. Wen replied to the last request within a day.
Keeps on schedule$8,950.00 in 2 invoices
import { AssistantMessage } from "@oration/canon/components/ai/message";import { RecommendationCard } from "@oration/canon/components/ai/recommendation-card";import { Button } from "@oration/canon/components/button";import { toast } from "@oration/canon/components/toast";export function InCopilot() {    return (        <div className="w-full max-w-xl rounded-xl bg-card p-4 text-left shadow-border">            <AssistantMessage>                <div className="flex flex-col gap-3">                    <p>                        Wen Zhou Consulting's W-9 expired in August, and two of                        their invoices are in Friday's run.                    </p>                    <RecommendationCard                        title="Request a new W-9 from Wen Zhou Consulting"                        rationale="Both invoices will be held on Friday without a current W-9. Wen replied to the last request within a day."                        confidence={0.81}                        impact={{                            label: "Keeps on schedule",                            value: "$8,950.00 in 2 invoices",                        }}                        actions={                            <Button                                type="button"                                variant="outline"                                size="sm"                                onClick={() =>                                    toast.add({                                        title: "Prompt sent",                                        description:                                            "Request a new W-9 from Wen Zhou Consulting",                                    })                                }                            >                                Request the W-9                            </Button>                        }                    />                </div>            </AssistantMessage>        </div>    );}

States#

States
StateTreatment
High confidence80% and above.
Medium confidence55% to 79%.
Low confidenceBelow 55%. Values outside 0 to 1 are clamped.
Acted onThe card has no built-in state. The assist panel swaps its actions for a green check and Done, and offers Undo in the toast.
DismissedAlso the caller's job: the assist panel removes the card on Not now.
Source hover and focusLinked source cards fill Well Gray on hover and draw a 3px Focus Indigo ring at 40% on focus.

Behavior#

  • It's a static <article>; only the buttons you pass in actions and linked sources are interactive.
  • confidence is 0 to 1. The meter rounds it to a whole percentage, and confidenceLabel(value) picks the band at 0.8 and 0.55.
  • impact as { label, value } renders the two-column well. Pass any other node for a custom well, or leave it out.
  • sources renders SourceCards in the row layout, numbered from 1.
  • The header wraps: on a narrow card the confidence meter drops under the title instead of squeezing it.

Do and don't#

High confidence
86%
Do. Show confidence in ink with its band word and percentage.
86%
Don't. Color it green for sure and red for unsure. Confidence isn't good or bad news, and color alone doesn't say how sure.

Email the ACH trace to Northwind Freight

High confidence
86%
Aisha called three times since Friday and asked for the trace each time.
Do. Write the rationale from the evidence: Northwind called three times since Friday and asked for the trace each time.

Email the ACH trace to Northwind Freight

High confidence
86%
AI thinks this is the best next step for this customer.
Don't. Write a rationale that only restates the model: AI thinks this is the best next step for this customer.

Content#

  • Title: the step, verb first, with the object: Email the ACH trace to Northwind Freight, Offer the early-payment discount explanation.
  • Rationale: one or two sentences of evidence: what was said, what the record shows. Name invoices and payments by ID.
  • Impact label: a verb or short noun, Avoids, Recovers, Saves. Value: the measured effect, a fourth call this week, $760.00 in disputed discount.
  • Actions: the step as a verb phrase, Send trace and remittance, plus a quiet Not now.

Accessibility#

  • The card is an <article> with an h3 title, so it's reachable by heading.
  • The confidence meter is role="meter" named Confidence, with the value out of 100. The band word and percentage are visible text beside it.
  • Source cards that link are anchors; external ones say (opens in a new tab) to screen readers.
  • Keyboard reaches only the actions and linked sources, in reading order.

Design tokens#

Design tokens
TokenUsed for
--cardCard surface
shadow-borderHairline lift
--mutedIcon tile, meter track, impact well at 70%
--foregroundMeter fill at 65%, title, impact value, percentage
--muted-foregroundBand word, rationale, impact label
--radius-xl12px card corners
--radius-lg10px impact well

API reference#

RecommendationCard

A proposed next step.

Other props spread onto Nothing. Only the props below are read..

Props of RecommendationCard
PropTypeDefaultDescription
titleRequiredstringNo defaultThe step, verb first.
rationaleRequiredReact.ReactNodeNo defaultThe evidence.
confidenceRequirednumberNo default0 to 1.
impact{ label: string; value: React.ReactNode } | React.ReactNodeNo defaultStructured for the two-column well, or any node.
actionsReact.ReactNodeNo defaultButtons. At most one filled.
iconReact.ReactNodeNo defaultShown in a 28px tile.
sources{ id: string; title: string; snippet: string; kind: "doc" | "url" | "record" | "transcript"; href?: string; score?: number; meta?: string }[]No defaultRendered as a source card row.
classNamestringNo defaultMerged onto the article.

ConfidenceMeter

The band word, an ink meter and the percentage. Works on its own.

Props of ConfidenceMeter
PropTypeDefaultDescription
valueRequirednumberNo default0 to 1, clamped.
classNamestringNo defaultMerged onto the wrapper.

confidenceLabel

Returns the band for a value.

Props of confidenceLabel
PropTypeDefaultDescription
valueRequirednumberNo default0 to 1.
returns"High confidence" | "Medium confidence" | "Low confidence"No defaultHigh at 0.8 and above, medium at 0.55 and above.

Known gaps#

Where the implementation and the system disagree today. Follow the system, not the gap.

The bands are fixed at 0.8 and 0.55; a page can't tune them for a model that runs hot or cold.

There's no built-in done, dismissed or loading state. The assist panel hand-rolls Done and Not now, and Copilot passes a single outline action.

The title is always an h3.

confidenceLabel is exported but not listed in the registry.