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.jsonUsage
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 / maxvalue on the right. When a row has abonus, 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 becomescb-warning, zero or unscored rows use the muted foreground. - Total — unless
hideTotalis set, the panel renders a computed total row at the bottom: the sum of every item'spointsplus anybonus, against the sum of everymax. - Animation — rows fade and slide in with a small stagger via
SPRINGS.smooth, capped at the canonicalSTAGGERconstant. The total row animates in last.prefers-reduced-motionsnaps everything to its final frame.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
items | ScoreBreakdownItem[] | required | Ordered breakdown rows. |
title | ReactNode | — | Optional headline above the list. |
hideTotal | boolean | false | Skip the computed total row. |
totalLabel | ReactNode | 'Total' | Label for the total row. |
baseDelay | number | 0 | Delay before the first row animates in (seconds). |
aria-label | string | 'Score breakdown' | Accessible label when no title is set. |
className | string | — | Merged onto the root via cn(). |
ScoreBreakdownItem
| Field | Type | Description |
|---|---|---|
id | string | Stable identifier, used as the React key. |
label | ReactNode | Visible label for the row. |
points | number | Points earned for this item. |
max | number | Maximum points possible for this item. |
bonus | number | Optional extra credit, surfaced as a chip. |
sublabel | ReactNode | Optional secondary annotation. |
Accessibility
- The panel root is a
<section>with eitheraria-labeloraria-labelledby(whentitleis set). - The rows are a
<ul role="list">withrole="listitem"children, so assistive tech treats the breakdown as a list of scored items. aria-live="polite"plusaria-atomic="false"scopes update announcements to whichever row changed.- Bonus chips carry their own
aria-labelso 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.