State & Feedback Systems

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.en

New project or installation trouble? Start here →

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

0.35
Active edited usageDefault props
<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.json

Or, 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

PropTypeDefaultDescription
questionsQuizQuestion[]—Question list
mode"single" | "multiple""single"Answer mode; questions whose answer is an array are always multiple-choice
durationnumber0.35Animation length in seconds for the check draw, explanation reveal and progress bar (also written to --wb-quiz-duration)
shuffleOptionsboolean | numberfalseShuffle option order; pass a number as a deterministic seed (same seed, same order — SSR safe)
allowBackbooleanfalseAllow going back to the previous question (answered questions stay locked)
valuenumber—Controlled current question index; managed internally when omitted
defaultValuenumber0Starting 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
labelsQuizCardLabels—Interface copy; omitted fields fall back to the defaults
classNamestring—Passed through to the outermost container

QuizQuestion

FieldTypeDefaultDescription
idstring—Question id; when set it seeds option shuffling and is used as the React key
qReactNode—Question content
optionsReactNode[]—Option content (in original order; answer uses indexes in this order)
answernumber | number[]—Index of the correct answer; pass an array for a multiple-choice question
explanationReactNode—Explanation revealed after answering

QuizCardLabels

FieldTypeDefaultDescription
progress(current: number, total: number) => string第 2 / 5 題 format (Question 2 / 5)Progress text
scorestring"分數" (Score)Score label
confirmstring"確認答案" (Check answer)Confirm button for multiple-choice questions
next / prevstring"下一題" / "上一題" (Next / Back)Navigation buttons
finishstring"看結果" (See results)Opens the end screen after the last question
restartstring"再玩一次" (Play again)Restart button on the end screen
correctstring"答對了" (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-duration is written from duration and 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.
  • shuffleOptions builds 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 are role="group" / role="checkbox", with aria-checked for 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