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.

Preview
Lesson complete

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.

Customize
Options

Installation

npx shadcn@latest add https://craftbits.dev/r/lesson-complete.json

Usage

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 in tabular-nums so columns line up across rows.
  • Journey — optional one-line narrative rendered above the action, painted in the muted tone with text-wrap: pretty.
  • Body slotchildren for any bespoke recap content (code bridges, recap chips, a full ScoreBreakdown, celebration confetti).
  • Action slot — the primary next-action; usually a Continue button. Render nothing when the parent surface owns the CTA.

Props

PropTypeDefaultDescription
titleReactNode'Lesson complete'Headline. Pass '' to suppress.
journeyReactNodeOptional one-line journey narrative above the action.
scoresreadonly LessonCompleteScoreRow[]Optional list of score rows. Each row: id, label, value.
childrenReactNodeBespoke recap content between score rows and the action.
actionReactNodePrimary next-action slot. Usually a Continue button.
srAnnouncementstringderived from titleScreen-reader announcement string.
regionAriaLabelstring'Lesson complete'Accessible name for the root region.
classNamestringMerged onto the root via cn().

LessonCompleteScoreRow

FieldTypeDescription
idstringStable identifier, used as the React key.
labelReactNodeShort label on the left of the row.
valueReactNodeValue on the right, painted with tabular-nums.

Accessibility

  • The root is a <div role="region"> with aria-label (defaults to Lesson 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: balance on the heading and text-wrap: pretty on 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-specific hex / trackHex / motionKey / LessonButton.Pill coupling and the PHASE_TRANSITION motion preset; lifted score rows from the ScoreRow shape coupled to ScoreBreakdown into 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 an action slot — callers pass their own button.