Score Breakdown

An itemized completion-screen primitive — a list of scored rows with optional bonus chips and an auto-computed total. Each row's tone is derived from its points-to-max ratio: full credit paints in success, partial in warning, zero or unscored rows in the muted foreground.

Preview
  • Navigate4/4
  • Code Bridgefill the blanks3/4
  • Race2/2
  • Total9/10
Customize
Options

Installation

npx shadcn@latest add https://craftbits.dev/r/score-breakdown.json

Usage

import { ScoreBreakdown } from "@craft-bits/core";
 
<ScoreBreakdown
  items={[
    { id: "navigate", label: "Navigate", points: 4, max: 4 },
    { id: "code-bridge", label: "Code Bridge", points: 3, max: 4 },
    { id: "race", label: "Race", points: 2, max: 2, bonus: 1 },
    { id: "synthesis", label: "Synthesis", points: 0, max: 3 },
  ]}
/>

With a headline and a custom total label:

<ScoreBreakdown
  title="Lesson recap"
  totalLabel="Final score"
  items={items}
/>

Anatomy

  • Row — small rounded card with a label on the left, an optional sublabel under it, and a tabular-nums points / max value on the right. When a row has a bonus, a small accent chip sits immediately before the value.
  • Tone — each row picks up a semantic tone from the points-to-max ratio: full credit becomes cb-success, partial becomes cb-warning, zero or unscored rows use the muted foreground.
  • Total — unless hideTotal is set, the panel renders a computed total row at the bottom: the sum of every item's points plus any bonus, against the sum of every max.
  • Animation — rows fade and slide in with a small stagger via SPRINGS.smooth, capped at the canonical STAGGER constant. The total row animates in last. prefers-reduced-motion snaps everything to its final frame.

Props

PropTypeDefaultDescription
itemsScoreBreakdownItem[]requiredOrdered breakdown rows.
titleReactNodeOptional headline above the list.
hideTotalbooleanfalseSkip the computed total row.
totalLabelReactNode'Total'Label for the total row.
baseDelaynumber0Delay before the first row animates in (seconds).
aria-labelstring'Score breakdown'Accessible label when no title is set.
classNamestringMerged onto the root via cn().

ScoreBreakdownItem

FieldTypeDescription
idstringStable identifier, used as the React key.
labelReactNodeVisible label for the row.
pointsnumberPoints earned for this item.
maxnumberMaximum points possible for this item.
bonusnumberOptional extra credit, surfaced as a chip.
sublabelReactNodeOptional secondary annotation.

Accessibility

  • The panel root is a <section> with either aria-label or aria-labelledby (when title is set).
  • The rows are a <ul role="list"> with role="listitem" children, so assistive tech treats the breakdown as a list of scored items.
  • aria-live="polite" plus aria-atomic="false" scopes update announcements to whichever row changed.
  • Bonus chips carry their own aria-label so screen readers announce them as bonuses, not as raw numbers.
  • Reduced-motion users get static rows — no enter animation.

Credits

  • Extracted from: AlgoFlashcards (src/lessons/primitives/chrome/ScoreBreakdown.tsx). The original was bound to a track-accent hex with check / cross row badges. This primitive generalises to itemized points / max, derives semantic tone from the ratio, and bakes in a computed total row.