Card Feedback

The response panel that sits below a problem-explainer card or quiz prompt after the learner picks. Pinned to one of four statuses — idle, correct, incorrect, partial — each driving the left rail color, the icon tint, the title color, and the politeness of the announced aria-live region.

Preview
Feedback
Pick an option to see feedback.
Customize
Status
Options

Installation

npx shadcn@latest add https://craftbits.dev/r/card-feedback.json

Usage

import { CardFeedback } from "@craft-bits/edu";
 
<CardFeedback
  status="correct"
  message="The denominator covers every position, so the weights always sum to 1."
  explanation="That is what makes the output a proper probability distribution."
  action={{ label: "Continue", onClick: handleContinue }}
/>

A bare placeholder before the learner has interacted:

<CardFeedback status="idle" message="Pick an option to see feedback." />

Anatomy

  • Status rail — a 3px left border painted with the tone color (--cb-success / --cb-error / --cb-warning / neutral). The fastest skim signal.
  • Status icon — a circular badge with a tinted background and an outline glyph (check / cross / warning dot). Hide via hideIcon or replace via icon.
  • Title — a small uppercase label painted in the tone color. Defaults to Correct / Not quite / Almost there / Feedback. Pass '' to suppress.
  • Message — long-form body text rendered with text-wrap: pretty. Hot-swaps on status change without a layout jump.
  • Explanation — optional smaller, muted paragraph rendered underneath the message for "why this is right/wrong" context.
  • Action slot — an optional inline button (Continue, Try again, etc.) painted in the same tone. Pass one action; render multi-button rows inside message if you need more.

Props

PropTypeDefaultDescription
status'idle' | 'correct' | 'incorrect' | 'partial''idle'Drives the rail color, icon tint, title color, and aria-live tone.
titleReactNodetone-derivedHeadline above the message. Pass '' to suppress.
messageReactNodeLong-form explanation. Rendered with text-wrap: pretty.
explanationReactNodeDeeper "why" context rendered underneath the message in a smaller, muted tone.
actionCardFeedbackActionOptional inline button (label, onClick, disabled, aria-label).
hideIconbooleanfalseSuppress the leading status icon.
iconReactNodetone-derived glyphOverride the default outline icon.
classNamestringMerged onto the root via cn().

Accessibility

  • The root is a <section role="status"> with aria-live matched to the status — assertive for incorrect, polite for correct / partial, off for idle.
  • The icon and status badge are aria-hidden="true"; the status meaning is conveyed by the title text, so screen-reader users hear the words Correct / Not quite / Almost there rather than the icon shape.
  • The action button enforces a 44px minimum hit area and shows a visible :focus-visible ring keyed to --cb-accent.
  • Color is never the only channel — the title text changes alongside the rail color, so the panel still reads correctly in monochrome or to color-blind viewers.

Credits

  • Extracted from: AlgoFlashcards (src/games/runtime/CardFeedback.tsx). Stripped the Convex useMutation, the patternId / gameType / cardId props, and the thumbs-up/down rating flow — generalized into a presentational panel that takes a status + message + explanation + action. The dev-only comment input dropped (out of scope for a library primitive).