Back to Blog
Guide

Quiz Accessibility: Making Your Quizzes WCAG Compliant

Make your quiz UI usable by everyone. Practical WCAG compliance for screen readers, keyboard navigation, color contrast, and focus management.

Bobby Iliev2026-04-086 min read

Accessibility Is Not a Feature, It Is a Requirement

If your quiz is not accessible, a significant portion of your users cannot take it. Screen reader users, keyboard-only navigators, users with low vision, and people with motor impairments all interact with your quiz differently. WCAG 2.1 AA compliance is the standard to target, and it is achievable without redesigning your entire UI.

This guide covers the specific accessibility challenges quizzes present and gives you code you can apply today.

Screen Reader Support

Screen readers need semantic HTML and ARIA attributes to convey quiz structure. The most common mistake is using <div> elements for everything.

Use Proper Form Elements

Radio buttons for single-answer questions, checkboxes for multiple-answer questions:

<fieldset>
  <legend>
    Question 3 of 10: What does the HTTP 204 status code mean?
  </legend>

  <div role="radiogroup" aria-labelledby="q3-legend">
    <label class="answer-option">
      <input type="radio" name="q3" value="a" />
      <span>No Content - the request succeeded with no response body</span>
    </label>

    <label class="answer-option">
      <input type="radio" name="q3" value="b" />
      <span>Not Found - the resource does not exist</span>
    </label>

    <label class="answer-option">
      <input type="radio" name="q3" value="c" />
      <span>Bad Request - the request was malformed</span>
    </label>

    <label class="answer-option">
      <input type="radio" name="q3" value="d" />
      <span>Unauthorized - authentication is required</span>
    </label>
  </div>
</fieldset>

The <fieldset> and <legend> elements group the question and answers. Screen readers announce "Question 3 of 10: What does the HTTP 204 status code mean? Group of 4 radio buttons."

Announce Score Results

When a user submits the quiz, announce the result to screen readers:

function QuizResults({ score, total }: { score: number; total: number }) {
  const percentage = Math.round((score / total) * 100);

  return (
    <div
      role="alert"
      aria-live="assertive"
      className="results-container"
    >
      <h2>Quiz Complete</h2>
      <p>
        You scored {score} out of {total} ({percentage}%).
      </p>
    </div>
  );
}

The role="alert" and aria-live="assertive" attributes ensure screen readers announce the result immediately.

Mark Correct and Incorrect Answers

After submission, visually show correct and incorrect answers but also communicate this to screen readers:

function AnswerFeedback({
  text,
  correct,
  selected,
}: {
  text: string;
  correct: boolean;
  selected: boolean;
}) {
  let srLabel = text;
  if (selected && correct) srLabel += " - Your answer, correct";
  else if (selected && !correct) srLabel += " - Your answer, incorrect";
  else if (correct) srLabel += " - Correct answer";

  return (
    <li
      className={`answer ${correct ? "correct" : ""} ${selected ? "selected" : ""}`}
      aria-label={srLabel}
    >
      <span aria-hidden="true">{correct ? "correct" : selected ? "incorrect" : ""}</span>
      <span>{text}</span>
    </li>
  );
}

Keyboard Navigation

Every quiz interaction must be reachable and operable with a keyboard alone.

Focus Management Between Questions

When the user moves to the next question, shift focus to the new question text. Without this, keyboard users are stuck at the "Next" button position:

function goToNextQuestion() {
  setCurrentIndex((prev) => prev + 1);

  // Shift focus to the question heading after render
  requestAnimationFrame(() => {
    const heading = document.getElementById("question-heading");
    heading?.focus();
  });
}

Make sure the heading is focusable:

<h2 id="question-heading" tabindex="-1">
  What is the output of typeof null?
</h2>

The tabindex="-1" makes the element focusable programmatically without adding it to the tab order.

Progress Indicator

A progress bar should be accessible:

<div
  role="progressbar"
  aria-valuenow="3"
  aria-valuemin="1"
  aria-valuemax="10"
  aria-label="Quiz progress: question 3 of 10"
>
  <div class="progress-fill" style="width: 30%"></div>
</div>

Keyboard Shortcuts

For power users, add keyboard shortcuts to navigate between questions. Always document them and make sure they do not conflict with screen reader shortcuts:

useEffect(() => {
  function handleKeydown(e: KeyboardEvent) {
    // Only when not in an input field
    if (e.target instanceof HTMLInputElement) return;

    switch (e.key) {
      case "ArrowRight":
        goToNextQuestion();
        break;
      case "ArrowLeft":
        goToPreviousQuestion();
        break;
      case "Enter":
        if (isLastQuestion) submitQuiz();
        break;
    }
  }

  document.addEventListener("keydown", handleKeydown);
  return () => document.removeEventListener("keydown", handleKeydown);
}, []);

Color Contrast

WCAG AA requires a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text. Quiz interfaces commonly fail contrast on:

  • Selected answer highlights (light blue on white)
  • Correct/incorrect indicators (light green or light red on white)
  • Disabled button states
  • Timer text on colored backgrounds

Use CSS custom properties so contrast is easy to audit and update:

:root {
  /* Text on white backgrounds - all 4.5:1+ contrast */
  --color-correct: #15803d;     /* green-700, 5.1:1 on white */
  --color-incorrect: #b91c1c;   /* red-700, 5.5:1 on white */
  --color-selected: #1d4ed8;    /* blue-700, 5.8:1 on white */
  --color-disabled: #6b7280;    /* gray-500, 4.6:1 on white */

  /* Background colors for answer states */
  --bg-correct: #dcfce7;        /* green-100 */
  --bg-incorrect: #fee2e2;      /* red-100 */
  --bg-selected: #dbeafe;       /* blue-100 */
}

.answer-option.correct {
  border-color: var(--color-correct);
  background: var(--bg-correct);
  color: var(--color-correct);
}

.answer-option.incorrect {
  border-color: var(--color-incorrect);
  background: var(--bg-incorrect);
  color: var(--color-incorrect);
}

Never rely on color alone to indicate correct or incorrect answers. Always pair color with text labels or icons.

Timer Accessibility

Timed quizzes present a unique challenge. Users who need more time due to a disability must be accommodated.

function QuizTimer({
  totalSeconds,
  allowExtension = true,
}: {
  totalSeconds: number;
  allowExtension?: boolean;
}) {
  const [remaining, setRemaining] = useState(totalSeconds);

  // Announce at key intervals
  useEffect(() => {
    if (remaining === 60 || remaining === 30 || remaining === 10) {
      const announcement = `${remaining} seconds remaining`;
      const el = document.getElementById("timer-announcement");
      if (el) el.textContent = announcement;
    }
  }, [remaining]);

  return (
    <div>
      <div
        role="timer"
        aria-live="off"
        aria-label={`${Math.floor(remaining / 60)} minutes ${remaining % 60} seconds remaining`}
      >
        {formatTime(remaining)}
      </div>

      {/* Polite announcements at key intervals */}
      <div
        id="timer-announcement"
        aria-live="polite"
        className="sr-only"
      />

      {allowExtension && remaining < 60 && (
        <button
          onClick={() => setRemaining((r) => r + 120)}
          aria-label="Add 2 minutes to the quiz timer"
        >
          Need more time?
        </button>
      )}
    </div>
  );
}

Setting aria-live="off" on the timer itself prevents screen readers from announcing every second. The separate announcement div with aria-live="polite" only speaks at meaningful intervals.

Testing Checklist

Run through this checklist before shipping:

  • Tab through the entire quiz flow - can you complete it without a mouse?
  • Turn on VoiceOver (macOS) or NVDA (Windows) and take the quiz - is every question and answer read correctly?
  • Check all color combinations with a contrast checker
  • Test with browser zoom at 200% - does the layout still work?
  • Verify focus moves logically when navigating between questions
  • Confirm results are announced to screen readers on submission

Summary

Quiz accessibility comes down to semantic HTML, proper ARIA attributes, keyboard operability, and sufficient color contrast. Use <fieldset> and <legend> for questions, manage focus on navigation, announce results with aria-live, and never rely on color alone to convey meaning.

These changes benefit all users, not just those with disabilities. Keyboard navigation helps power users, clear contrast helps everyone in bright sunlight, and semantic markup improves SEO.

Stay Updated

Get the latest tutorials and API tips delivered to your inbox.

No spam, unsubscribe anytime.

Enjoyed this article?

Share it with your team or try our quiz platform.