Lesson Complete
The per-lesson celebration card that lands at the end of a single skill-card lesson. A compact stack of: an optional headline, an optional list of score rows, an optional journey-narrative line, a bespoke content slot, and an action slot for the Continue button.
Distinct from CompletionScreen — that one is the page-level template (title + verdict + headline score + stats grid + action) you ship at the end of a whole module. LessonComplete is the smaller, inline-friendly card you stack inside a single lesson surface. Strictly presentational — owns no scoring, streak math, or progression logic.
Lesson complete
- Navigate
- 4/4
- Code Bridge
- 3/4
- Race
- 2/2
- Synthesis
- 3/3
You discovered that scanning once is enough — every duplicate must collide with a slot you have already seen.
Installation
npx shadcn@latest add https://craftbits.dev/r/lesson-complete.jsonUsage
import { LessonComplete } from "@craft-bits/core";
<LessonComplete
title="Lesson complete"
scores={[
{ id: "navigate", label: "Navigate", value: "4/4" },
{ id: "code-bridge", label: "Code Bridge", value: "3/4" },
]}
journey="You discovered that scanning once is enough."
action={<button type="button">Continue</button>}
/>A minimal celebration with just a title and a Continue button:
<LessonComplete
title="Lesson complete"
action={<button type="button">Continue</button>}
/>A celebration card without an internal CTA — the parent lesson surface owns the Continue button:
<LessonComplete
title="Lesson complete"
scores={scores}
journey={journey}
/>Anatomy
- Title — heading at the top. Defaults to
Lesson complete. Pass""to suppress. - Score rows — optional
<dl>stack of label / value pairs, with values painted intabular-numsso columns line up across rows. - Journey — optional one-line narrative rendered above the action, painted in the muted tone with
text-wrap: pretty. - Body slot —
childrenfor any bespoke recap content (code bridges, recap chips, a fullScoreBreakdown, celebration confetti). - Action slot — the primary next-action; usually a Continue button. Render nothing when the parent surface owns the CTA.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
title | ReactNode | 'Lesson complete' | Headline. Pass '' to suppress. |
journey | ReactNode | — | Optional one-line journey narrative above the action. |
scores | readonly LessonCompleteScoreRow[] | — | Optional list of score rows. Each row: id, label, value. |
children | ReactNode | — | Bespoke recap content between score rows and the action. |
action | ReactNode | — | Primary next-action slot. Usually a Continue button. |
srAnnouncement | string | derived from title | Screen-reader announcement string. |
regionAriaLabel | string | 'Lesson complete' | Accessible name for the root region. |
className | string | — | Merged onto the root via cn(). |
LessonCompleteScoreRow
| Field | Type | Description |
|---|---|---|
id | string | Stable identifier, used as the React key. |
label | ReactNode | Short label on the left of the row. |
value | ReactNode | Value on the right, painted with tabular-nums. |
Accessibility
- The root is a
<div role="region">witharia-label(defaults toLesson complete). - A visually-hidden
role="status"aria-live="polite"region announces the title on mount. - The score list is a
<dl>with<dt>labels and<dd>values so assistive tech treats the rows as definition pairs. - Text uses
text-wrap: balanceon the heading andtext-wrap: prettyon the journey paragraph to avoid orphan words. - Motion respects
prefers-reduced-motion— the enter animation is skipped entirely when the user prefers reduced motion.
Credits
- Extracted from:
AlgoFlashcards(src/lessons/primitives/chrome/LessonComplete.tsx). Stripped the project-specifichex/trackHex/motionKey/LessonButton.Pillcoupling and thePHASE_TRANSITIONmotion preset; lifted score rows from theScoreRowshape coupled toScoreBreakdowninto a thin{id,label,value}row so the card can show simple per-act scores without dragging the full points-breakdown primitive. The primary CTA is now anactionslot — callers pass their own button.