- Published on
Web Accessibility 101: Build an Accessible Quiz from the Ground Up
- Authors
- Name
- Md Nasim Sheikh
- @nasimStg
'Web Accessibility 101: Build an Accessible Quiz from the Ground Up'
Learn to build a web quiz that's accessible to everyone. This step-by-step guide covers semantic HTML, ARIA, keyboard navigation, and inclusive design.
Table of Contents
- 'Web Accessibility 101: Build an Accessible Quiz from the Ground Up'
- Section 1: The Bedrock of Accessibility - Semantic HTML
- Structuring the Quiz
- Structuring the Questions and Answers
- Section 2: Keyboard Navigation is Non-Negotiable
- The All-Important Focus Indicator
- Section 3: Communicating with Screen Readers (ARIA)
- Announcing Feedback with ARIA Live Regions
- Connecting Feedback to Questions with aria-describedby
- Section 4: Designing for Everyone - Color, Contrast, and Content
- Color Contrast
- Don't Rely on Color Alone
- Clear and Simple Language
- Section 5: Putting It All Together - The JavaScript Logic
- Conclusion: Your Accessibility Checklist
You've just had a brilliant idea: a fun, engaging quiz for your website. Maybe it's a personality test, a knowledge check for an online course, or just a quirky "Which houseplant are you?" quiz. You fire up your code editor, ready to bring it to life. But hold on a second. Before you write a single <div>
, let's ask a critical question: will everyone be able to take your quiz?
Web accessibility (often abbreviated as a11y) isn't a feature or an add-on; it's a fundamental practice of building websites that work for all people, regardless of their ability, disability, or the technology they use. For an interactive element like a quiz, it's absolutely essential. An inaccessible quiz can be a frustrating, impossible barrier for users relying on screen readers, keyboards, or other assistive technologies.
In this guide, we're going to build a simple, multi-question quiz from the ground up, focusing on accessibility at every single step. We'll cover the non-negotiables: semantic structure, keyboard navigation, screen reader communication, and inclusive design. By the end, you'll have a solid foundation for making all your future interactive content more accessible.
Section 1: The Bedrock of Accessibility - Semantic HTML
Before you even think about fancy JavaScript animations or slick CSS, you need a rock-solid HTML structure. Semantic HTML means using HTML elements for their intended purpose. This is the single most important thing you can do for accessibility because it provides meaning and structure that browsers and assistive technologies understand right out of the box.
A quiz is, at its core, a form. So, let's start there.
Structuring the Quiz
Instead of a pile of <div>
s, we'll use elements that describe the content's role.
<form>
: The container for our entire quiz. It tells the browser, "Hey, this is a collection of interactive controls."<fieldset>
: This is a magic element for quizzes. It groups related form controls. For us, afieldset
will contain a single question and all its possible answers.<legend>
: Thelegend
is the caption for its parentfieldset
. It acts as the label for the entire group, which is perfect for our question text. A screen reader will announce thelegend
when the user navigates into thefieldset
, providing crucial context.
Structuring the Questions and Answers
Inside each fieldset
, we need to structure the answer options.
<input type="radio">
: Radio buttons are the natural choice for a multiple-choice question where only one answer can be selected.<label>
: This is the partner to your<input>
. Never use an input without a corresponding label. Thefor
attribute of the label must match theid
of the input. This connection is vital for two reasons:- It increases the clickable area. Users can click on the label text, not just the tiny radio button.
- It explicitly links the text to the control for screen readers. When a screen reader user focuses on the radio button, it will read the label's text aloud.
Let's see what the HTML for a single question looks like:
<form id="a11y-quiz">
<fieldset>
<legend>Question 1: What does the 'A' in 'ARIA' stand for?</legend>
<div>
<input type="radio" id="q1-a1" name="question1" value="accessible">
<label for="q1-a1">Accessible</label>
</div>
<div>
<input type="radio" id="q1-a2" name="question1" value="advanced">
<label for="q1-a2">Advanced</label>
</div>
<div>
<input type="radio" id="q1-a3" name="question1" value="animated">
<label for="q1-a3">Animated</label>
</div>
</fieldset>
<!-- More questions would go here as additional fieldsets -->
<button type="submit">Check Answer</button>
</form>
Notice the name="question1"
attribute is the same for all radio buttons in this group. This is standard HTML that ensures only one can be selected at a time. By using this structure, we've already built a quiz that's remarkably robust and understandable to assistive tech.
Section 2: Keyboard Navigation is Non-Negotiable
Not everyone uses a mouse. Users with motor impairments, power users who prefer keyboards, and screen reader users all rely on the keyboard to navigate web content. The good news? Because we used semantic HTML (<input>
, <button>
), we get a lot of this for free!
Try it with the HTML above. You can use the Tab
key to move between the radio button group and the submit button. Once you're focused on a radio button, you can use the arrow keys (Up
/Down
/Left
/Right
) to change your selection. This is the default, predictable behavior that keyboard users expect.
The All-Important Focus Indicator
When you tab through the page, you'll see a faint blue or black outline around the focused element. This is the focus indicator. It's the keyboard user's equivalent of a mouse cursor, showing them where they are on the page.
One of the most common accessibility mistakes is to remove this outline with CSS like this:
/* DON'T DO THIS! */
:focus {
outline: none;
}
Doing this makes your site completely unusable for keyboard-only users. If you don't like the default outline, that's fine—but you must replace it with something better. A good custom focus style should have high contrast and be clearly visible.
Here’s an example of a much better, accessible custom focus style:
/* A better, accessible custom focus style */
:focus-visible {
outline: 3px solid #005fcc; /* A nice, high-contrast blue */
outline-offset: 2px;
box-shadow: 0 0 0 5px rgba(0, 95, 204, 0.3);
border-radius: 2px;
}
We use :focus-visible
here. This is a modern pseudo-class that typically shows the focus style only for keyboard users, not for mouse clicks, which can be a nice design touch while preserving accessibility.
Section 3: Communicating with Screen Readers (ARIA)
Our quiz is static right now. When a user submits an answer, things will change on the page. We need to provide feedback: Was the answer correct? What's the right answer? How do we communicate these dynamic updates to a user who can't see the screen?
This is where ARIA (Accessible Rich Internet Applications) comes in. ARIA is a set of attributes you can add to HTML elements to provide extra information to assistive technologies, especially for dynamic content.
Announcing Feedback with ARIA Live Regions
When the user clicks "Check Answer," we'll want to display a message like "Correct!" or "Incorrect." A sighted user can see this message appear, but a screen reader won't know about it unless we tell it to pay attention.
We can do this with an ARIA live region. This is an element that's designated to announce any changes to its content. The most common attribute is aria-live
.
aria-live="polite"
: This is the most common setting. It tells the screen reader to wait until the user has finished their current task (e.g., finished reading the current line) before announcing the change. It's not disruptive.aria-live="assertive"
: This is for urgent, important updates. It will interrupt the screen reader immediately to announce the change. Use this sparingly, as it can be very jarring. For our quiz feedback,polite
is perfect.
Let's add an empty container for our feedback messages to our HTML, somewhere logical on the page.
<!-- Add this inside your form, perhaps after the button -->
<div id="quiz-feedback" aria-live="polite" class="feedback-container"></div>
Now, when our JavaScript adds content to this <div>
, a screen reader will automatically read it aloud. It's that simple!
aria-describedby
Connecting Feedback to Questions with What if we want to provide more specific feedback for each question? We can add a message right next to the question itself. But how do we programmatically link that feedback to the question group so a screen reader user understands the connection?
We use aria-describedby
. This attribute's value is the id
of another element on the page. It tells the screen reader, "The element with this ID provides a description for me."
Let's modify our fieldset
and add a placeholder for this feedback.
<fieldset aria-describedby="q1-feedback">
<legend>Question 1: What does the 'A' in 'ARIA' stand for?</legend>
<!-- ... radio buttons ... -->
<div id="q1-feedback" class="feedback-message"></div>
</fieldset>
When a screen reader user focuses on any of the radio buttons inside this fieldset
, it will announce the question (legend
), the selected radio button's label, and then it will also read the content of the div
with the ID q1-feedback
. This is incredibly powerful for providing contextually relevant information.
Section 4: Designing for Everyone - Color, Contrast, and Content
Accessibility isn't just about code; it's also about design. Visual choices can either include or exclude users.
Color Contrast
Users with low vision or color blindness may struggle to read text if it doesn't have sufficient contrast with its background. The Web Content Accessibility Guidelines (WCAG) define specific contrast ratios to follow:
- AA Standard: A contrast ratio of at least 4.5:1 for normal text.
- AAA Standard: A contrast ratio of at least 7:1 for normal text (a higher standard).
Don't guess! Use tools to check your colors. The Chrome DevTools accessibility inspector, Figma plugins, or the WebAIM Contrast Checker are all excellent resources.
Bad Example: Light gray text on a white background (#999
on #FFF
) has a ratio of 2.32:1. Fails. Good Example: Dark gray text on a white background (#333
on #FFF
) has a ratio of 12.14:1. Passes with flying colors!
Don't Rely on Color Alone
This is a classic mistake in quiz design. You mark correct answers with green and incorrect answers with red. This is meaningless to a user who is colorblind or using a monochrome screen.
Always use more than just color to convey information. Combine color with:
- Text: Explicitly write "Correct" or "Incorrect."
- Icons: Use a checkmark (✓) for correct and a cross (✗) for incorrect. Make sure these icons have proper alt text or are implemented as accessible SVGs.
<!-- Good example of feedback -->
<div class="feedback-correct">
<svg role="img" aria-label="Correct"><!-- checkmark icon --></svg>
<span>Correct! The answer is Accessible.</span>
</div>
Clear and Simple Language
Your quiz is not the place for complex jargon or convoluted sentences (unless that's what you're testing!). Write questions, instructions, and feedback in plain language that's easy to understand. This benefits everyone, including people with cognitive disabilities, non-native speakers, and even users who are just distracted.
Section 5: Putting It All Together - The JavaScript Logic
Now let's bring our accessible quiz to life with JavaScript. We'll handle form submission, check answers, and update the UI and our ARIA attributes.
First, let's define our quiz data.
const quizData = [
{
question: "What does the 'A' in 'ARIA' stand for?",
answers: ["Accessible", "Advanced", "Animated"],
correctAnswer: "Accessible",
name: "question1"
},
{
question: "Which HTML element is used to group related form controls?",
answers: ["<div>", "<group>", "<fieldset>"],
correctAnswer: "<fieldset>",
name: "question2"
}
// Add more questions here
];
Now, let's write the script to handle the quiz logic. We'll listen for the form's submit
event.
// This is a simplified example. A real app might be more complex.
document.addEventListener('DOMContentLoaded', () => {
const quizForm = document.getElementById('a11y-quiz');
const feedbackContainer = document.getElementById('quiz-feedback');
let currentQuestionIndex = 0; // We'll handle one question at a time
// For this example, let's assume the HTML for the first question is already on the page.
quizForm.addEventListener('submit', (event) => {
event.preventDefault(); // Stop the page from reloading
const currentQuestionData = quizData[currentQuestionIndex];
const questionFieldset = quizForm.querySelector(`fieldset[data-question-index="${currentQuestionIndex}"]`);
// Find the selected radio button
const selectedRadio = quizForm.querySelector(`input[name="${currentQuestionData.name}"]:checked`);
if (!selectedRadio) {
feedbackContainer.textContent = "Please select an answer.";
return; // Don't proceed if no answer is selected
}
const userAnswer = selectedRadio.value;
const isCorrect = userAnswer === currentQuestionData.correctAnswer;
const feedbackMessageEl = document.getElementById(`${currentQuestionData.name}-feedback`);
let feedbackText;
if (isCorrect) {
feedbackText = `Correct! Well done.`;
feedbackMessageEl.classList.add('correct');
} else {
feedbackText = `Incorrect. The correct answer is: ${currentQuestionData.correctAnswer}`;
feedbackMessageEl.classList.add('incorrect');
}
// 1. Update the question-specific feedback
feedbackMessageEl.textContent = feedbackText;
// 2. Announce the result globally via the live region
feedbackContainer.textContent = `For question ${currentQuestionIndex + 1}: ${feedbackText}`;
// 3. Disable the inputs for the answered question
const inputs = questionFieldset.querySelectorAll('input');
inputs.forEach(input => input.disabled = true);
// 4. Move focus for a better user experience
// Focus on the feedback message so the user can immediately read/hear it.
feedbackMessageEl.setAttribute('tabindex', -1); // Make it programmatically focusable
feedbackMessageEl.focus();
// You could add logic here to show the next question
// currentQuestionIndex++;
// displayNextQuestion();
});
});
Let's break down the key accessibility steps in this JavaScript:
event.preventDefault()
: We handle the submission ourselves without a page reload, which is crucial for a smooth single-page experience.- Update the Feedback: We populate both the question-specific feedback (
aria-describedby
) and the global live region (aria-live
). This provides redundant, robust communication. - Disable Answered Questions: We disable the inputs of the answered question. This prevents users from changing their answers and reduces confusion about which question is currently active.
- Manage Focus: This is a pro-level tip. After the user acts, don't make them hunt for the result. By programmatically moving focus to the feedback message (
feedbackMessageEl.focus()
), we guide the user directly to the new information. This creates a seamless flow, especially for screen reader and keyboard users.
Conclusion: Your Accessibility Checklist
Building an accessible quiz isn't about memorizing a hundred ARIA attributes. It's about a shift in mindset—from assuming how users will interact with your content to intentionally building it for everyone.
We've covered a lot of ground. The next time you build a quiz or any interactive component, run through this mental checklist:
- ✅ Semantic HTML: Is it built with
<form>
,<fieldset>
,<legend>
, and<label>
? Is the core structure meaningful? - ✅ Keyboard Navigable: Can you complete the entire quiz using only the
Tab
,Arrow
, andEnter
/Space
keys? - ✅ Visible Focus: Is the keyboard focus indicator always visible and clear?
- ✅ Screen Reader Communication: Are dynamic changes (like feedback) announced clearly using ARIA live regions?
- ✅ Inclusive Design: Does it meet color contrast standards? Is information conveyed by more than just color?
- ✅ Clear Language: Are the instructions and content simple and easy to understand?
- ✅ Sensible Focus Management: After an action, are you guiding the user to the result by moving focus?
Start with these principles, and you'll not only be complying with best practices but also creating better, more robust, and more user-friendly experiences for all. Happy coding!