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.jsonUsage
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
- 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.
- Options are radio buttons. The options render as a
role="radiogroup"labelled by the question; each option is arole="radio"button witharia-checkedreflecting selection. The first interactive press emitsonAnswer(idx, correct). - Controlled + uncontrolled.
selectedIdx+onAnsweris the Radix controlled pattern.defaultSelectedIdxlets the component own selection internally. If neither is provided, no option starts selected. - Explanation is opt-in. When
explanationis 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. - Hit target. Every option row enforces a 44 × 44px minimum hit area (per WCAG 2.5.8) regardless of label length.
- 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
| Prop | Type | Default | Description |
|---|---|---|---|
question | ReactNode | required | The question prompt rendered above the options. |
options | readonly string[] | required | The answer options. |
correctIdx | number | required | The index of the correct option in options. |
explanation | ReactNode | — | Revealed once the student answers. |
selectedIdx | number | null | — | Controlled selection. Pair with onAnswer. |
defaultSelectedIdx | number | null | null | Uncontrolled initial selection. |
onAnswer | (idx: number, correct: boolean) => void | — | Fires every time the student picks (or re-picks) an option. |
editable | boolean | true | When false, the options become non-interactive. |
tone | "default" | "accent" | "success" | "warning" | "error" | "accent" | Tone for the selected-option ring and explanation banner. |
header | ReactNode | — | Content rendered above the question. |
footer | ReactNode | — | Content rendered below the options. |
transition | Transition | SPRINGS.smooth | Override option / explanation transitions. Reduced-motion users snap regardless. |
className | string | — | Merged onto the outer <div> via cn(). |
Accessibility
- The option list is a
role="radiogroup"labelled by the question; each option is arole="radio"button witharia-checkedreflecting 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 aLessonContext(auto-routingonScreenComplete), atrackHexaccent that leaked into the explanation border, a per-row shake animation on wrong picks, and amisconceptionanalytics 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.