React Quiz Component
ExamplesHeadless

All questions and review

Show every question together or paginate a quiz, edit selections, review answers, and submit once using the existing headless API.

Try the quiz

Answer in any order. Change single choices, toggle multiple choices, or switch the number of questions per page without losing answers. Correctness and scores appear only after submission.

Implementation

This is a custom headless layout; the default Quiz component continues to show one question at a time. Install react-quiz-component, then copy the component, quiz.ts, and examples.css into the same directory. Next.js applications need a client boundary; the preview mounts in the browser.

Render state.questionStates rather than just the current question. allowNavigation permits targeting any question, and revealAnswerOnSubmit keeps choices editable until submission. Each click first calls goToQuestion(index) and, only if it succeeds, selectAnswer(answerNumber). Both actions see the latest session within the same React event.

Pagination only changes which questions are displayed. state.canSubmit checks the entire quiz, including hidden pages, and requires exactly the requested number of multiple selections. The summary and question outcomes are available internally before submission; this example deliberately renders them only after completion. It uses text questions without a timer; add image or audio rendering separately when your data requires it.

all-questions.tsx
'use client';

import { useState } from 'react';
import { useQuiz } from 'react-quiz-component';
import { quiz } from './quiz';
import './examples.css';

export default function AllQuestionsExample() {
  const { state, actions } = useQuiz({ quiz, allowNavigation: true, revealAnswerOnSubmit: true });
  const [pageSize, setPageSize] = useState(quiz.questions.length);
  const [page, setPage] = useState(0);
  const pageCount = Math.ceil(state.questions.length / pageSize);

  return (
    <section className="headless-demo" aria-label="Reviewable quiz">
      {state.status === 'invalid' && <p role="alert">{state.errors.join(' ')}</p>}
      {state.status === 'idle' && <>
        <h2>{quiz.quizTitle}</h2>
        <p>Answer the questions, review your choices, then submit.</p>
        <button onClick={actions.start}>Start quiz</button>
      </>}
      {state.status === 'active' && <>
        <label>Questions per page{' '}
          <select value={pageSize} onChange={(event) => { setPageSize(Number(event.target.value)); setPage(0); }}>
            <option value={state.questions.length}>All questions</option>
            {state.questions.slice(1).map((_, index) => <option key={index + 1} value={index + 1}>{index + 1}</option>)}
          </select>
        </label>
        {state.questionStates.slice(page * pageSize, (page + 1) * pageSize).map(({ question, index, answers }) => (
          <fieldset key={index} className="question-group">
            <legend>{index + 1}. {question.question}</legend>
            <p>Select {Array.isArray(question.correctAnswer) ? question.correctAnswer.length : 1} answer(s).</p>
            <div className="answers">
              {answers.map((answer) => <button key={answer.answerNumber}
                disabled={answer.disabled} aria-pressed={answer.selected}
                onClick={() => {
                  // Actions share the latest session even within one React event.
                  if (actions.goToQuestion(index).ok) actions.selectAnswer(answer.answerNumber);
                }}>
                {answer.value}
              </button>)}
            </div>
          </fieldset>
        ))}
        {pageCount > 1 && <nav aria-label="Question pages">
          <button disabled={page === 0} onClick={() => setPage(page - 1)}>Previous page</button>
          <span>Page {page + 1} of {pageCount}</span>
          <button disabled={page + 1 === pageCount} onClick={() => setPage(page + 1)}>Next page</button>
        </nav>}
        <p role="status">{state.canSubmit ? 'Review your answers, then submit.' : 'Select the requested number of answers for every question.'}</p>
        <button disabled={!state.canSubmit} onClick={actions.submitQuiz}>Submit quiz</button>
      </>}
      {state.status === 'completed' && <>
        <output>Score: {state.summary.correctPoints} / {state.summary.totalPoints}</output>
        <ol>
          {state.questionStates.map(({ question, index, answers, outcome }) => <li key={index}>
            <strong>{question.question}</strong>
            <p>Your answers: {answers.filter((answer) => answer.selected).map((answer) => answer.value).join(', ')} — {outcome}</p>
          </li>)}
        </ol>
        <button onClick={() => { actions.reset(); setPage(0); }}>Try again</button>
      </>}
    </section>
  );
}

Custom styles

examples.css
.headless-demo {
  display: grid;
  gap: 16px;
}
.headless-demo h2 {
  font-size: 1.2rem;
  font-weight: 600;
}
.headless-demo .answers {
  display: grid;
  gap: 8px;
}
.headless-demo .question-group {
  display: grid;
  min-inline-size: 0;
  gap: 8px;
  padding: 16px;
  border: 1px solid var(--color-fd-border, #d4d4d8);
  border-radius: 8px;
}
.headless-demo .question-group legend {
  max-inline-size: 100%;
  padding-inline: 4px;
  font-weight: 600;
}
.headless-demo button,
.headless-demo select {
  border: 1px solid var(--color-fd-border, #d4d4d8);
  border-radius: 8px;
  padding: 8px 12px;
  margin: 4px 8px 4px 0;
  cursor: pointer;
  background: var(--color-fd-background, #fff);
  color: var(--color-fd-foreground, #18181b);
}
.headless-demo button:hover {
  background: var(--color-fd-muted, #f4f4f5);
}
.headless-demo button[aria-pressed='true'] {
  border-color: var(--color-fd-primary, #2563eb);
  background: var(--color-fd-accent, #eff6ff);
}
.headless-demo button:disabled {
  opacity: 0.65;
  cursor: default;
}
.headless-demo :focus-visible {
  outline: 2px solid var(--color-fd-primary, #2563eb);
  outline-offset: 3px;
}

Quiz data

quiz.ts
import type { Quiz } from 'react-quiz-component';

export const quiz: Quiz = {
  quizTitle: 'React essentials',
  quizSynopsis: 'Two questions about React hooks and rendering.',
  questions: [
    {
      question: 'Which hook stores local state?',
      questionType: 'text',
      answerSelectionType: 'single',
      answers: ['useState', 'useEffect', 'useRef'],
      correctAnswer: '1',
      point: 10,
      messageForCorrectAnswer: 'Correct! useState stores component state.',
      messageForIncorrectAnswer: 'useState is the hook for local state.',
      explanation: 'useState returns the current value and a setter.',
    },
    {
      question: 'Which APIs render React content? Pick two.',
      questionType: 'text',
      answerSelectionType: 'multiple',
      answers: ['createRoot', 'renderToString', 'querySelector'],
      correctAnswer: [1, 2],
      point: 20,
      explanation:
        'createRoot renders in the browser; renderToString renders HTML on the server.',
    },
  ],
};

On this page