A11y Quiz Form

Progressive quiz behavior for semantic HTML forms, with retry feedback, single- and multiple-answer scoring, and practice, exam, and review modes.

Quick facts

Package
a11y-quiz-form
Format
TypeScript source, ESM output
Runtime dependencies
None
Best for
One quiz question per native form

Live demo: Single answer

Choose an answer and check it. A wrong first attempt reveals targeted feedback; a correct or final answer reveals all explanations.

Practice · 2 attempts · Radio buttons

Which HTML element should be used for a clickable form action?

Choose one answer.

Need a hint?

Think about elements that work with keyboard and submit forms by default.

  • Topic: Forms
  • WCAG: 4.1.2 Name, Role, Value
  • Difficulty: Beginner

Multiple answers — Checkbox quiz

Practice · 2 attempts · Checkboxes

Which of the following are valid ways to associate a label with an input?

Choose all answers that apply.

Need a hint?

There are at least two native HTML mechanisms for linking a label to an input.

  • Topic: Forms
  • WCAG: 1.3.1 Info and Relationships
  • Difficulty: Intermediate

Exam mode — One attempt, no hint

Exam · 1 attempt · No hint

According to WCAG 2.2, what is the minimum contrast ratio for normal text?

Choose one answer.

Need a hint?

Look up WCAG Success Criterion 1.4.3.

  • Topic: Visual Design
  • WCAG: 1.4.3 Contrast (Minimum)
  • Difficulty: Beginner

Review mode — Already answered

Review · Read-only · All explanations visible

What does ARIA stand for?

You previously answered this question.

  • Topic: ARIA
  • WCAG: 4.1.2 Name, Role, Value
  • Difficulty: Beginner

Focused standalone examples

Use these smaller pages when you need one behavior, configuration boundary, or lifecycle question without the full documentation page around it.

Basic single answer

A copy-pasteable radio-button quiz with two attempts and progressive feedback.

Open the basic example

Multiple-answer scoring lab

Matched checkbox quizzes make exact versus partial scoring easy to compare.

Open the scoring lab

Spanish localization example

Test Spanish validation, retry, progress, result, reveal, and answer-state messages.

Open the localization example

State playground

Apply mode and option presets from controls kept outside the runtime plugin form.

Open the state playground

Teardown and reinitialize lab

Inspect cleanup artifacts, exact author-markup equality, restoration, and reinitialization.

Open the lifecycle lab

Dynamic content refresh lab

Replace answer markup while inspecting selection migration, focus recovery, and refresh event details.

Open the refresh lab

Installation

The repository is configured for npm packaging. Confirm registry availability before using the package name in a production install.

npm

npm install a11y-quiz-form

pnpm

pnpm add a11y-quiz-form

Yarn

yarn add a11y-quiz-form

Basic usage

Supply the semantic form markup. The plugin adds feedback behavior and live-region relationships; it does not generate the question or answers.

HTML

<form class="a11y-quiz" data-a11y-quiz novalidate>
  <fieldset class="a11y-quiz__fieldset">
    <legend class="a11y-quiz__question">Which element is a button?</legend>
    <p class="a11y-quiz__instruction">Choose one answer.</p>

    <div class="a11y-quiz__answers">
      <div class="a11y-quiz__answer" data-correct="true">
        <label class="a11y-quiz__answer-label">
          <input class="a11y-quiz__input" type="radio" name="answer" value="button">
          <span class="a11y-quiz__answer-text">&lt;button&gt;</span>
        </label>
        <p class="a11y-quiz__explanation" hidden>A button has native semantics.</p>
      </div>
    </div>

    <p class="a11y-quiz__status"></p>
    <div class="a11y-quiz__actions">
      <button type="submit">Check answer</button>
      <button type="button" data-quiz-show-answer hidden>Show answer</button>
      <button type="button" data-quiz-reset hidden>Try again</button>
    </div>
  </fieldset>
</form>

JavaScript

import { createQuizForm } from "a11y-quiz-form";
import "a11y-quiz-form/styles.css";

const form = document.querySelector("[data-a11y-quiz]");

if (form instanceof HTMLFormElement) {
  createQuizForm(form, { maxAttempts: 2 });
}

See the focused basic example for a complete standalone file.

Options

Pass options to createQuizForm() or use the matching data-* attributes. Programmatic options take precedence and invalid values fall back to the defaults.

Option Type Default Purpose
mode "practice" | "exam" | "review" "practice"
"practice"
Uses your configured attempt limit, hints, answer reveal, reset, and explanation settings for a guided learning flow.
"exam"
Allows one check, hides hints and answer reveal, and shows all explanations after that final attempt. Reset availability still follows allowReset; set it to false for a single-submission exam.
"review"
Starts with the answer and all explanations revealed. Inputs are disabled, actions are hidden, and check, reveal, and reset calls do nothing.
maxAttempts number 2 Sets allowed checks from 1 through 100; exam mode always uses one.
explanationMode QuizFormExplanationMode selected-after-wrong Controls feedback visibility after a retryable incorrect answer.
showAnswerMode QuizFormShowAnswerMode after-first-wrong Controls when the optional reveal button becomes available.
scoringMode "exact" | "partial" | "all-or-nothing" exact
"exact"
Requires every correct option and no incorrect options.
"partial"
Accepts any non-empty subset containing only correct options. This is subset acceptance, not a numeric partial-credit score.
"all-or-nothing"
Compatibility alias of "exact"; it uses the same complete-set rule.
allowReset boolean true Enables reset() and shows the reset action after completion.
disableAfterComplete boolean true Disables answer inputs after completion or reveal.
focusResult boolean false Moves focus to the result summary after completion; default behavior preserves focus.
announceChanges boolean true Writes feedback to the polite status region.
shuffleAnswers boolean false Randomizes answer-card DOM order once during initialization.
showProgress boolean false Adds an attempt progress indicator.
disableCheckUntilAnswered boolean true Disables the submit button until at least one answer is selected.
messages QuizFormMessages English catalog Partially overrides validation, status, progress, summary, and answer-state text.

Localization

Pass a partial typed message catalog. Strings work for fixed copy; callbacks receive attempt, maxAttempts, and remainingAttempts for grammar and pluralization. Missing, empty, invalid, or throwing overrides safely use the existing English default.

import { createQuizForm, type QuizFormMessages } from "a11y-quiz-form";

const messages = {
  validationRequired: "Elige al menos una respuesta.",
  correct: ({ attempt }) => `Correcto en el intento ${attempt}.`,
  incorrectRetry: ({ remainingAttempts }) =>
    remainingAttempts === 1
      ? "Queda un intento."
      : `Quedan ${remainingAttempts} intentos.`,
} satisfies QuizFormMessages;

createQuizForm(form, { messages });

Keep live-region and validation messages concise, meaningful, and non-empty. Include the result or action and any remaining-attempt context needed without relying on nearby visual content. Callbacks intentionally receive attempt counts, not selected values. Do not close over or place private response data, personal information, or secure answer material in messages because the text can be visible and announced by assistive technology.

The exported QuizFormMessages, QuizFormMessageContext, and QuizFormMessageKey types define the catalog. See the repository README for the complete message-key table and English default purposes.

API and events

Method Description
createQuizForm(form, options?) Enhances one form and reuses its existing instance.
initQuizForms(options?, root?) Enhances every declarative form in a document or parent node.
quizFormEvents Provides frozen constants for every public lifecycle event name.
onQuizFormEvent(target, name, listener, options?) Adds a typed lifecycle listener and returns idempotent cleanup.
check() Checks the current answer selection.
reveal() Reveals the correct answers and explanations.
reset() Returns the instance to its initial interaction and checked/disabled input state.
getState() Returns state, attempts, selected values, and correctness.
destroy() Removes listeners and generated feedback, restores author-owned state and answer order, then emits the destroy event.

Events

Events dispatch synchronously from the form, bubble, do not cross a shadow boundary, and are non-cancelable. Their detail includes instance and the compatibility alias quiz, plus the fields below.

Event Trigger Additional detail
a11yquiz:initEnhancement begins after initial state capture.
a11yquiz:readyEnhancement and initial synchronization finish.
a11yquiz:changeA managed input changes.selected
a11yquiz:checkAn active check is accepted.selected, valid, attempt
a11yquiz:correctCorrect-result UI finishes.attempt
a11yquiz:incorrectIncorrect-result UI finishes.attempt, canRetry
a11yquiz:retryA retry is available.attempt
a11yquiz:revealAnswer-reveal UI finishes.
a11yquiz:resetAn accepted reset finishes.
a11yquiz:completeA run first reaches a terminal result.correct, revealed, attempt
a11yquiz:destroyCleanup and author-DOM restoration finish.

Guaranteed sequences include init → ready, check → correct → complete, check → incorrect → retry, and check → incorrect → complete. When reveal first completes a run it emits reveal → complete; a later permitted reveal emits only reveal.

Accessibility behavior

Structure and feedback

  • Starts with a native form, fieldset, legend, labels, radio buttons, checkboxes, and buttons.
  • Associates instructions, hints, answer notes, statuses, and visible explanations with each input while preserving author-provided description references.
  • Uses an assertive validation message for a missing answer and polite status regions for quiz feedback.
  • Adds visible text such as “Correct answer” and “Your answer: incorrect,” so color is not the only signal.

Focus and display

  • Preserves focus by default; optional result focusing is explicit.
  • Uses native disabled states after completion and restores initial checked and disabled input state on reset.
  • Restores author-owned ARIA, text, focusability, hidden state, button state, and answer order when the instance is destroyed.
  • Includes visible focus styles, forced-colors rules, reduced-motion handling, and a no-JavaScript answer fallback in the examples.
  • Does not add application-widget roles to native form controls.

Keyboard interaction

Key Behavior
Tab / Shift+Tab Moves through the native form controls, hint summary, and action buttons.
Arrow keys Uses the browser’s native radio-group selection behavior.
Space Selects a focused radio button or checkbox, or activates a focused button.
Enter Activates a focused button and submits the quiz through its native form behavior.

Styling hooks

Stable classes

The stylesheet uses the .a11y-quiz block with BEM elements including __fieldset, __answer, __input, __actions, __status, and __explanation. Runtime states use .is-selected, .is-correct, and .is-incorrect.

Public custom properties

Theme the component with prefixed properties such as --a11y-quiz-color-action-bg, --a11y-quiz-color-focus-ring, --a11y-quiz-color-correct-bg, --a11y-quiz-radius, and --a11y-quiz-max-width. Properties beginning with --_ are private implementation details.

.a11y-quiz {
  --a11y-quiz-color-action-bg: #5b21b6;
  --a11y-quiz-color-action-hover: #4c1d95;
  --a11y-quiz-color-focus-ring: #fbbf24;
  --a11y-quiz-radius: 0.75rem;
}

Limitations and support

  • The plugin manages one question per form; it does not aggregate scores across a quiz or persist results.
  • Question and answer markup must follow the documented class and data-attribute contract.
  • Correct answers are stored in client-side data-correct attributes. Exam mode changes interaction, but it is not a secure assessment system.
  • The plugin does not sanitize author-provided question, hint, or explanation HTML.
  • Screen reader output still depends on correct, concise content and should be tested with the combinations your product supports.
  • No formal browser compatibility matrix or WCAG conformance claim is included. The build targets modern ESM-capable browsers.

Treat this package as an interaction layer. Content design, answer quality, localization, end-to-end assistive-technology testing, and server-side assessment security remain the integrating application’s responsibility.