Quiz Card

A multiple-choice quiz card. The caller supplies a question, a list of options, the index of the correct option (correctIdx), and an optional explanation rendered once the student answers. The card owns selection state, locks after a correct pick, emits onAnswer(idx, correct), and animates the option ring + explanation with reduced-motion fallbacks. Controlled (selectedIdx + onAnswer) and uncontrolled (defaultSelectedIdx) on the Radix pattern.

In a 2-D convolution, what does the stride parameter control?
Customize
Highlight
1
Behaviour

Installation

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

Usage

import { QuizCard } from "@craft-bits/core";
 
const OPTIONS = [
  "Stride controls how far the kernel moves between applications.",
  "Stride controls how many input channels the kernel reads.",
  "Stride controls the size of the output activation map only.",
  "Stride controls how many filters are applied in parallel.",
];
 
<QuizCard
  question="In a 2-D convolution, what does the stride parameter control?"
  options={OPTIONS}
  correctIdx={0}
  explanation="Stride is the step size of the kernel across the input."
/>

Controlled — parent owns the selection so it can score, advance phases, or persist:

const [selectedIdx, setSelectedIdx] = useState<number | null>(null);
 
<QuizCard
  question="What does stride control?"
  options={OPTIONS}
  correctIdx={0}
  selectedIdx={selectedIdx}
  onAnswer={(idx, correct) => {
    setSelectedIdx(idx);
    if (correct) advancePhase();
  }}
/>

Read-only review — render the correct answer without letting the student re-pick:

<QuizCard
  question="What does stride control?"
  options={OPTIONS}
  correctIdx={0}
  selectedIdx={0}
  editable={false}
  tone="success"
/>

Understanding the component

  1. Pick, then lock. Picking an option reveals the explanation and a tone-coded ring (success for the correct option, warning for a distractor). Once the correct option is picked, the panel locks — every other option fades and disables.
  2. Options are radio buttons. The options render as a role="radiogroup" labelled by the question; each option is a role="radio" button with aria-checked reflecting selection. The first interactive press emits onAnswer(idx, correct).
  3. Controlled + uncontrolled. selectedIdx + onAnswer is the Radix controlled pattern. defaultSelectedIdx lets the component own selection internally. If neither is provided, no option starts selected.
  4. Explanation is opt-in. When explanation is provided, it slides in below the options once the student answers — coloured with the success ramp on a correct pick or the tone variable on a wrong pick.
  5. Hit target. Every option row enforces a 44 × 44px minimum hit area (per WCAG 2.5.8) regardless of label length.
  6. Reduced motion. Option ring transitions, tap scale, and explanation enter/exit collapse to instant under prefers-reduced-motion: reduce. The selection still locks; only the motion drops.

Props

PropTypeDefaultDescription
questionReactNoderequiredThe question prompt rendered above the options.
optionsreadonly string[]requiredThe answer options.
correctIdxnumberrequiredThe index of the correct option in options.
explanationReactNodeRevealed once the student answers.
selectedIdxnumber | nullControlled selection. Pair with onAnswer.
defaultSelectedIdxnumber | nullnullUncontrolled initial selection.
onAnswer(idx: number, correct: boolean) => voidFires every time the student picks (or re-picks) an option.
editablebooleantrueWhen false, the options become non-interactive.
tone"default" | "accent" | "success" | "warning" | "error""accent"Tone for the selected-option ring and explanation banner.
headerReactNodeContent rendered above the question.
footerReactNodeContent rendered below the options.
transitionTransitionSPRINGS.smoothOverride option / explanation transitions. Reduced-motion users snap regardless.
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 × 44px minimum hit area (per WCAG 2.5.8 AAA) regardless of label width.
  • The explanation banner uses role="status" + aria-live="polite" so the student's pick announces without stealing focus.
  • Options expose data-state (rest / selected / correct / wrong) so consumer apps can hook custom styles or assistive tooling.
  • Tone is never the only signal — selected, correct, and wrong states layer fill, ring, and indicator-dot changes so colour-blind users see the distinction.
  • Motion respects prefers-reduced-motion: reduce — option ring transitions and explanation enter/exit collapse to instant.

Credits

  • Extracted from: algoflashcards (src/lessons/primitives/interaction/QuizCard.tsx). The source coupled the card to a LessonContext (auto-routing onScreenComplete), a trackHex accent that leaked into the explanation border, a per-row shake animation on wrong picks, and a misconception analytics tag. The library extract drops the lesson-context dependency and the analytics tag, swaps the inline track-hex border for the cb-token tone ramp, replaces the bespoke shake / pop animations with the canonical SPRINGS.smooth transition + TAP_SCALE press feedback, and surfaces the controlled / uncontrolled selection pair so consumers can score, persist, or advance phases on top of it.