Components/RecommendationCard

RecommendationCard

An agent suggestion with its confidence: rationale, alternatives drawer, and the action that confirms it — all without the card changing shape.

VerifiedSince 0.3.9

Usage

A Suggestion and Its Alternatives

The footer states confidence as a meter and as words. "Alternatives" opens the drawer, and picking one promotes it to the current recommendation.

Loading demo...

Best Practices

  • Write short so it reads on its own — inside the drawer it is the only information there is.
  • Pair label with the meter rather than relying on colour; colour drops out under high contrast and for colour-blind readers.
  • Use ctaTone: 'danger' for destructive actions so the primary button's weight matches the consequence.
  • Lift accepted into the host and set it true only after the request succeeds, or readers will believe the order was placed.
  • Keep alternatives to two or three; beyond that the choice belongs on a list page, not in a drawer.

API Reference

Props

PropTypeDefaultDescription
titlestring—Card heading, usually the question awaiting confirmation. Required.
optionsRecommendationOption[]—The options: { key, text?, short, confidence?, signal?, tone?, label, cta?, ctaTone? }. Required.
modelValuestringfirst option's keyv-model, key of the promoted recommendation.
openboolean—v-model:open, the alternatives drawer. Omit to let the card own it.
acceptedboolean—v-model:accepted, whether it was confirmed.
alternativesLabelstring'Alternatives'Text of the drawer toggle.
otherOptionsLabelstring'Other options'Heading inside the drawer.
acceptedLabelstring'Accepted'Primary action text once confirmed.
acceptLabelstring'Accept'Fallback text for an option without its own cta.

Events

EventArgumentsDescription
update:modelValue(key: string)The promoted option changed.
update:open(open: boolean)The drawer opened or closed.
update:accepted(accepted: boolean)The confirmed state changed.
accept(option: RecommendationOption)The primary action was pressed, carrying the whole option.
select(option: RecommendationOption)An alternative was picked in the drawer.

Slots

SlotScopeDescription
body{ option }Replaces the rationale. Rich content goes here: code for identifiers, mark for entities.
meter{ option }Replaces a meter, used in the footer and in every drawer row.
footer-extra—Inserted on the left of the footer, before the actions.

How Confidence Maps

confidence is the semantic entry point; the component derives the meter's fill and colour from it:

confidenceSegmentsDefault colour
high3--tx-bui-green
medium2--tx-bui-orange
low1--tx-bui-red
none (default)0--tx-bui-ink-3

signal and tone are the escape hatches, overriding the count and the colour respectively. label is always required — colour cannot be the only carrier of state.

Overview

  • Picking promotes. Choosing an alternative makes it the current recommendation, closes the drawer, and clears the confirmed state — a confirmation of the previous option must not carry over to a new one.
  • The drawer lists only the other options; the current recommendation never appears twice.
  • The collapsed drawer is inert. A 0fr grid only squeezes the height to zero, leaving its buttons in the tab order; the component marks it inert to take them out of the accessibility tree and the focus order. This is a deliberate improvement over upstream.
  • accepted only swaps text and colour and has no undo. Real workflows usually need one, so it is a controlled prop and the host owns the retraction.
  • The rationale has a minimum height (48px by default) so the card does not jump as options of different lengths swap in. Line heights differ by script; tune it with --tx-bui-recommendation-card-body-min-height.
  • Both <code> and <mark> inside the rationale are styled by the component, so filling the body slot with rich content needs no styling of its own:
    • <code> is a real identifier — a SKU, a filename, a command. An accent tint by default, switched with the is-success / is-warning classes.
    • <mark> is an entity the suggestion refers to — a supplier, a file, a person. It renders as a pill with a colour dot, not monospaced, because this is a name to read rather than a string to type. The host supplies the dot colour through --tx-entity-color (only the host knows what colour a given supplier is); without it the dot falls back to neutral grey.
    • <mark> rather than a convention class, because the element already means "text singled out for reference" and carries that to a screen reader instead of relying on colour.
  • tone takes a raw CSS colour string and does not follow the theme; prefer confidence.
  • The drawer eases more softly than the rest of this family (cubic-bezier(0.16, 1, 0.3, 1)). That difference is deliberate upstream and is preserved.

Technologies

  • Component source: packages/tuffex/packages/components/src/recommendation-card/src/TxRecommendationCard.vue.
  • Types: packages/tuffex/packages/components/src/recommendation-card/src/types.ts.
  • Verified coverage: packages/tuffex/packages/components/src/recommendation-card/__tests__/recommendation-card.test.ts (14 cases) and recommendation-card-inline.test.ts (9 compiled-CSS contracts: code staying monospaced with its semantic variants on the BUI token layer, mark as a non-mono pill, the engine default mark highlight being overridden, and the dot reading --tx-entity-color with a fallback) covers promoting the first option by default, the confidence to segments-and-colour mapping, signal / tone overrides, the drawer listing only alternatives, paired aria-expanded / aria-controls, inert while collapsed, promotion clearing the confirmed state, ctaTone mapping, controlled modelValue / accepted precedence, the cta fallback, an empty list rendering nothing, and rich content through the body slot.
  • Adapted from Beautiful UI (https://www.beautifului.dev), © 2026 Shane Levine, MIT.
查看源码
packages/tuffex/packages/components/src/recommendation-card/index.ts
  • How it divides from TxToolConfirmation: that is a binary authorisation (allow / deny plus a risk tier); this is a multi-option suggestion with evidence — an option list, a confidence level, and promotion semantics. Only the two footer buttons look alike.
  • The meter is its own primitive: the footer and every drawer row render TxSignalMeter, which can be used independently — see its own doc.
  • Accessibility: inert on the collapsed drawer corrects an upstream defect.
  • Known deviation: the primary button's box-shadow uses the same hardcoded rgba(16,24,40,·) values in both themes upstream. That is preserved to match the reference screenshots — it reads as a bevel on a solid fill rather than as a themed hairline.