Quiz Card
A single- or multiple-choice quiz card React component. The correct option draws a check, wrong picks shake, an explanation expands and the next question slides in, with progress, a rolling score and an end screen.
A quiz card for courses, campaign pages or product tours. Single-choice questions are judged on the first click; for multiple-choice questions, tick your picks and press Check answer. Once judged, the correct option turns green and a check draws itself inside the circle on the right, wrong picks turn red and shake horizontally, the remaining options fade, and the explanation expands in height. Press Next or Enter and the current question slides out to the left while the next one slides in from the right; a progress bar and a digit-by-digit rolling score sit on top, and after the last question an end screen shows the total, a verdict and each question's result — click any question to review it, or Play again.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add quiz-card.enPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<QuizCard/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
Installation
npx webberui@latest add https://webberui.com/r/quiz-card.en.jsonOr, once registries are configured in components.json, install it as @webberui/quiz-card.
Usage
import { QuizCard } from "@/components/ui/quiz-card";
<QuizCard
questions={[
{
q: "What is the highest mountain in Taiwan?",
options: ["Xueshan", "Yushan", "Hehuanshan", "Alishan"],
answer: 1,
explanation: "Yushan's main peak stands at about 3,952 m.",
},
{
q: "Which of these are outlying islands of Taiwan? (select all)",
options: ["Penghu", "Kinmen", "Orchid Island", "Okinawa"],
answer: [0, 1, 2],
},
]}
allowBack
onAnswer={(index, correct) => console.log(index, correct)}
onComplete={(score, total) => console.log(`${score} / ${total}`)}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
questions | QuizQuestion[] | — | Question list |
mode | "single" | "multiple" | "single" | Answer mode; questions whose answer is an array are always multiple-choice |
duration | number | 0.35 | Animation length in seconds for the check draw, explanation reveal and progress bar (also written to --wb-quiz-duration) |
shuffleOptions | boolean | number | false | Shuffle option order; pass a number as a deterministic seed (same seed, same order — SSR safe) |
allowBack | boolean | false | Allow going back to the previous question (answered questions stay locked) |
value | number | — | Controlled current question index; managed internally when omitted |
defaultValue | number | 0 | Starting question index in uncontrolled mode |
onChange | (index: number) => void | — | Called when the current question changes |
onAnswer | (index: number, correct: boolean) => void | — | Called after each question is judged with the question index and whether it was correct |
onComplete | (score: number, total: number) => void | — | Called when the end screen opens with the score and the total |
labels | QuizCardLabels | — | Interface copy; omitted fields fall back to the defaults |
className | string | — | Passed through to the outermost container |
QuizQuestion
| Field | Type | Default | Description |
|---|---|---|---|
id | string | — | Question id; when set it seeds option shuffling and is used as the React key |
q | ReactNode | — | Question content |
options | ReactNode[] | — | Option content (in original order; answer uses indexes in this order) |
answer | number | number[] | — | Index of the correct answer; pass an array for a multiple-choice question |
explanation | ReactNode | — | Explanation revealed after answering |
QuizCardLabels
| Field | Type | Default | Description |
|---|---|---|---|
progress | (current: number, total: number) => string | 第 2 / 5 題 format (Question 2 / 5) | Progress text |
score | string | "分數" (Score) | Score label |
confirm | string | "確認答案" (Check answer) | Confirm button for multiple-choice questions |
next / prev | string | "下一題" / "上一題" (Next / Back) | Navigation buttons |
finish | string | "看結果" (See results) | Opens the end screen after the last question |
restart | string | "再玩一次" (Play again) | Restart button on the end screen |
correct | string | "答對了" (Correct!) | Text announced on a correct answer |
wrong | (answer: string) => string | 答錯了,正確答案是 B format (Not quite — the correct answer is B) | Text announced on a wrong answer |
verdicts | [string, string, string] | ["太厲害了", "不錯喔", "再接再厲"] (Outstanding! / Nicely done / Keep practicing) | Verdicts by score ratio (80% and up, 50% and up, otherwise) |
You can also override quiz, multipleHint, keyHint, backToResult, correctTag, wrongTag, resultTitle, review, answeredCorrect, answeredWrong and unanswered; see the JSDoc in the source for details.
How it works
- Colours can be overridden with CSS variables:
--wb-quiz-correct(default emerald-500) and--wb-quiz-wrong(default red-500);--wb-quiz-durationis written fromdurationand drives the progress bar width transition. - Question changes use
AnimatePresence mode="wait"with a direction: moving forward, the question slides out left and the next slides in from the right (spring stiffness 300, damping 30); going back or reviewing reverses it. - The score rolls digit by digit inside the component: each digit rolls in from below, and only the digit that changed moves.
shuffleOptionsbuilds the permutation from a deterministic hash, so server and browser render the same order; the A, B, C letters and number keys follow the displayed order.- Answered questions are locked; with
allowBack, or when clicking a question on the end screen, the review shows the recorded result without replaying the animation.
Accessibility
- Single-choice options are
role="radiogroup"/role="radio"(roving tabindex), multiple-choice options arerole="group"/role="checkbox", witharia-checkedfor the picked state; Arrow keys, Home and End move between options - Number keys 1–9 pick the matching option, Enter confirms a multiple-choice answer or moves to the next question; keys pressed while an IME (e.g. Chinese) is composing are ignored
- The result is announced through
aria-live="assertive"as “Correct!” or “Not quite — the correct answer is B” - Correct and wrong are conveyed by check / cross icons and “Correct” / “Wrong” tags as well as colour; the end screen list has screen-reader text for each question
- The top progress bar is
role="progressbar"; the rolling score animation is hidden from assistive tech and exposed as plain text instead - When the user has Reduce motion turned on, wrong picks do not shake, the check appears instantly, and question changes cross-fade
Spinner Set
Four React loading indicators — ring, dots, bars and a tracing brand mark — with adjustable size, speed and label, falling back to a static icon with text under reduced motion.
Drawn Capsule Stepper
An SVG stroke draws a capsule outline, then step pills inside light up in sequence — for setup wizards, task progress and onboarding flows.