Synthesis Quiz

A data-driven multi-question quiz primitive. The caller supplies an array of questions, a pair of done-screen messages (perfect, needsWork), and an optional passThreshold — the panel walks through every question, tracks first-attempt correctness, and finishes with a synthesis grade screen that reports the score and the matching message.

Question 1 / 3
Pruning
Which signal best tells you a neuron has stopped contributing to the loss?
Customize
Scoring
2
Behaviour

Installation

npx shadcn@latest add https://craftbits.dev/r/synthesis-quiz.json

Usage

import { SynthesisQuiz } from "@craft-bits/core";
 
const QUESTIONS = [
  {
    scenario: "Pruning",
    question: "Which signal best tells you a neuron has stopped contributing?",
    options: [
      "Its gradient stays at or near zero across many batches.",
      "Its weight magnitude is large.",
      "Its activation is non-negative on every input.",
      "Its incoming weights all share the same sign.",
    ],
    correctIdx: 0,
    explanation: "A dead neuron is one whose gradient flow has collapsed.",
  },
  // ...more questions
];
 
<SynthesisQuiz
  questions={QUESTIONS}
  doneMessages={{
    perfect: "Three for three. You can ship the lesson.",
    needsWork: "Re-read the explanations — the gaps are in the norm intuition.",
  }}
  passThreshold={2}
  onComplete={(score) => persistResult(score)}
/>

Disable retry on wrong, so the first pick locks the row regardless of correctness:

<SynthesisQuiz
  questions={QUESTIONS}
  doneMessages={{ perfect: "Clean.", needsWork: "Try again." }}
  retryOnWrong={false}
/>

Surface a hint after every retry — pass a string or a function of the wrong-attempt count:

<SynthesisQuiz
  questions={QUESTIONS}
  doneMessages={{ perfect: "Clean.", needsWork: "Try again." }}
  wrongHint={(n) => (n === 1 ? "Look at the gradient signal." : "Hint two.")}
/>

Understanding the component

  1. One row, locked once committed. Each question renders as a question prompt + scenario badge + option list. Picking the correct option commits the row, reveals the explanation, and surfaces a Next question button. Wrong picks bounce back to an unselected state under the default retryOnWrong policy.
  2. First-attempt scoring. A question only counts toward the synthesis score if the student picks the correct option on their first attempt. Retried-into-correct picks still advance the quiz, but the final score reflects how much the student knew up front.
  3. Synthesis grade screen. Once the last question is answered, the panel swaps to a centred grade card with the score (e.g. 2/3) and the perfect or needsWork message — perfect only renders when every question was correct on the first attempt.
  4. Pass threshold. passThreshold (default 2) sets the minimum first-attempt correct count required to pass; the result is reported in the passed field handed to onComplete.
  5. Streak badge. A streak N chip appears in the header once the student has two or more consecutive first-attempt correct answers; a wrong pick resets the streak to zero.
  6. Wrong-hint slot. In retry mode, an optional wrongHint (string or function of wrongAttempts) renders between the question and the options after the first miss.
  7. Reduced motion. Option ring transitions, the explanation enter / exit, the Next question button, and the wrong-hint banner all collapse to instant fades under prefers-reduced-motion: reduce. Scoring and locking still apply; only the motion drops.

Props

PropTypeDefaultDescription
questionsreadonly SynthesisQuizQuestion[]requiredList of questions in display order.
doneMessages{ perfect, needsWork }requiredMessages for the synthesis grade screen.
retryOnWrongbooleantrueBounce wrong picks back so the student can retry.
wrongHintstring | (n: number) => stringHint shown after a wrong attempt in retry mode.
passThresholdnumber2First-attempt correct count required to pass.
tone"default" | "accent" | "success" | "warning" | "error""accent"Tone for the selected-option ring and the scenario badge.
onCorrect(idx, streak) => voidFires after every correct answer.
onWrong(idx, attempts) => voidFires after every wrong answer.
onComplete(score: SynthesisQuizScore) => voidFires once the last question is answered.
transitionTransitionSPRINGS.smoothOverride option / explanation / button transitions.
classNamestringMerged onto the outer <div> via cn().

Accessibility

  • The option list is a role="radiogroup" labelled by the question; each option is a role="radio" button with aria-checked reflecting selection.
  • Every option is keyboard activated — Tab to focus, Space or Enter to pick — and renders a visible focus ring through focus-visible:ring-cb-accent.
  • All interactive targets enforce a 44 x 44px minimum hit area (per WCAG 2.5.8 AAA) regardless of label width.
  • The explanation banner, the wrong-hint banner, and the synthesis grade screen all use role="status" + aria-live="polite" so a pick announces without stealing focus.
  • Options expose data-state (rest / selected / correct / wrong); the panel exposes data-state (active / done) and data-perfect on the grade screen so consumer apps can hook custom styles or assistive tooling.
  • Motion respects prefers-reduced-motion: reduce — option ring transitions, the explanation banner, the next-question button, and the wrong-hint banner collapse to instant fades.

Credits

  • Extracted from: algoflashcards (src/lessons/primitives/interaction/SynthesisQuiz.tsx). The source bound useMultiQuiz to a useLessonContext + trackHex accent, the LessonButton.Pill atom, a per-question Visual slot, the QuizDoneScreen + TrophyReveal celebration components, and the project-level playSound channel. The library extract collapses the hook + multi-atom composition into a single self-contained component, drops the lesson-context dependency in favour of an explicit passThreshold + onComplete callback, swaps the inline track accents for the tone token ramp, and replaces the trophy celebration with an inline grade card. First-attempt scoring is preserved verbatim — only a question solved on the first try counts toward the synthesis grade.