Completion Celebration

A celebratory status card for the moment a multi-step flow completes — a lesson, a recipe, a quiz, an onboarding sequence. Renders a tone-coloured icon disc with a checkmark that strokes itself in, a serif headline, an optional message, and an optional call-to-action. A small confetti burst fires from the center on mount, then settles. The "peak end" of a completion experience.

Lesson complete

Nice work — you finished all 4 steps.

Customize
Tone
success
Options

Installation

npx shadcn@latest add https://craftbits.dev/r/completion-celebration.json

Usage

import { CompletionCelebration } from "@craft-bits/core";
 
<CompletionCelebration
  title="Lesson complete"
  message="Nice work — you finished all 4 steps."
  cta={{ label: "Next lesson", onClick: goNext }}
/>

Understanding the component

  1. Tone-driven swatch. tone selects a success / accent / warning palette that paints the icon disc, the ring around it, the checkmark stroke, and the confetti dots. All four reuse the --cb-* semantic tokens, so a re-theme repaints the whole celebration.
  2. Drawn-in checkmark. The default icon is a single SVG path animated via a pathLength tween with SPRINGS.smooth. With reduced motion on, the stroke is fully drawn on the first frame — no animation, no flash.
  3. Card entrance. The root is a motion.div that scales from 0.9 to 1 and fades in with SPRINGS.smooth. Reduced-motion users get a plain opacity fade with DURATIONS.fast.
  4. Deterministic confetti. Eighteen particles are positioned by hashing useId(), so the layout is identical on server and client — no Math.random() in render. Each particle flies from the center at a random angle out to 56-112px and fades over 0.22-0.30s. Reduced motion suppresses the burst entirely.
  5. Polite ARIA. The root carries role="status" and aria-live="polite", with an aria-label of "Completion: <title>" so screen readers announce the finish without interrupting the user.
  6. Controlled visibility. visible={false} unmounts the card. Re-mount the parent with a fresh key to re-trigger the entrance animation — useful in docs demos and in flows that need the celebration to "play again." The customizer above uses the same trick with its Replay button.

Props

PropTypeDefaultDescription
visiblebooleantrueWhen false, nothing renders. Key-remount the parent to re-trigger the entrance.
titleReactNode"Complete!"Headline. Strings render in the serif display style.
messageReactNodeOptional supporting line beneath the title.
cta{ label; onClick?; href? }Optional call-to-action. Renders as a button (or anchor when href is set).
tone"success" | "accent" | "warning""success"Palette for the disc, ring, checkmark, and confetti.
confettibooleantrueWhether a confetti burst fires on mount. Auto-disabled under reduced motion.
iconReactNodedefault checkmarkOverride the centred icon. Should fit a 32×32 box.
classNamestringMerged onto the root via cn().

Accessibility

  • Root uses role="status" and aria-live="polite" so the completion is announced without interrupting other speech.
  • The aria-label defaults to "Completion: <title>" when the title is a string.
  • Confetti is decorative — its container is aria-hidden.
  • Reduced motion: the card uses a short fade, the checkmark renders fully drawn from the first frame, and the confetti burst is suppressed.
  • Tone colours all clear WCAG AA contrast against --cb-bg-elevated.

Credits

  • Extracted from: terminal-dreams (src/components/cookbook/CompletionCelebration.tsx). The library version is a re-architecture: the source was a fixed-position fullscreen overlay with a 3D card flip and recipe-specific copy. The craft-bits version is a non-modal status card with generalised props (title, message, cta), tone-aware theming via --cb-* tokens, deterministic confetti from useId() instead of Math.random(), motion via SPRINGS.smooth / EASINGS.out, and a stroke-drawn checkmark in place of the recipe emoji.