HTML5 Accessibility: The Interview Question That Shows You Build for Everyone

·12 min read
html5interview-questionsaccessibilitya11yfrontend

Over 1 billion people worldwide live with some form of disability, and web accessibility lawsuits in the US have increased 300% since 2018. Yet 96% of home pages have detectable WCAG failures. This isn't just a legal risk—it's a massive gap between what developers know they should do and what they actually implement. Here's how to nail accessibility questions in interviews.

The 30-Second Answer

When the interviewer asks "What is web accessibility?", here's your concise answer:

"Web accessibility means building websites that everyone can use, including people with visual, hearing, motor, or cognitive disabilities. The key practices are: use semantic HTML elements instead of divs, provide alt text for images, ensure keyboard navigation works, maintain sufficient color contrast, and use ARIA attributes when native HTML isn't enough. The standard is WCAG 2.1, and most sites target Level AA compliance."

Wait for follow-up questions.

The 2-Minute Answer (If They Want More)

If they ask you to elaborate:

"Accessibility, often called a11y, is about removing barriers. Different users need different accommodations:

  • Blind users rely on screen readers that read content aloud
  • Low-vision users need zoom and high contrast
  • Deaf users need captions and transcripts
  • Motor-impaired users navigate with keyboards only
  • Cognitively impaired users need clear language and consistent layouts

The foundation is semantic HTML - using button instead of div, nav for navigation, main for content. Screen readers use these to help users navigate.

ARIA fills gaps when HTML isn't enough - like aria-label for icon buttons or aria-live for dynamic updates.

Testing is crucial: keyboard-only navigation, screen reader testing, automated tools like axe or Lighthouse, and manual review.

It's not just ethics - it's often legal. ADA lawsuits against inaccessible websites are increasing, and it also improves SEO since search engines rely on the same semantic structure."

Code Examples to Show

Semantic HTML vs Div Soup

<!-- BAD: Div soup - no meaning to screen readers -->
<div class="header">
  <div class="nav">
    <div class="nav-item" onclick="goHome()">Home</div>
    <div class="nav-item" onclick="goAbout()">About</div>
  </div>
</div>
<div class="content">
  <div class="article">
    <div class="title">Article Title</div>
    <div class="text">Article content...</div>
  </div>
</div>
 
<!-- GOOD: Semantic HTML - meaningful to everyone -->
<header>
  <nav aria-label="Main navigation">
    <ul>
      <li><a href="/">Home</a></li>
      <li><a href="/about">About</a></li>
    </ul>
  </nav>
</header>
<main>
  <article>
    <h1>Article Title</h1>
    <p>Article content...</p>
  </article>
</main>

Accessible Button vs Fake Button

<!-- BAD: Div pretending to be a button -->
<div class="btn" onclick="submit()">Submit</div>
<!-- Problems: Not focusable, no keyboard support, not announced as button -->
 
<!-- GOOD: Native button element -->
<button type="submit">Submit</button>
<!-- Benefits: Focusable, Enter/Space work, announced as "Submit, button" -->
 
<!-- If you MUST use a div (rare), add accessibility: -->
<div
  role="button"
  tabindex="0"
  onclick="submit()"
  onkeydown="if(event.key === 'Enter' || event.key === ' ') submit()"
>
  Submit
</div>
<!-- But why do all this when <button> does it automatically? -->

Image Accessibility

<!-- Informative image - describe the meaning -->
<img
  src="chart.png"
  alt="Sales increased 50% from Q1 to Q2 2024"
 />
 
<!-- Decorative image - empty alt -->
<img src="decorative-border.png" alt="" />
 
<!-- Complex image - extended description -->
<figure>
  <img
    src="complex-chart.png"
    alt="Quarterly sales comparison"
    aria-describedby="chart-desc"
   />
  <figcaption id="chart-desc">
    Q1: $1M, Q2: $1.5M (50% increase), Q3: $1.8M, Q4: $2.1M.
    Total annual growth: 110%.
  </figcaption>
</figure>
 
<!-- Icon button - aria-label for meaning -->
<button aria-label="Close dialog">
  <svg aria-hidden="true"><!-- X icon --></svg>
</button>

The Classic Problem: Focus Management in Modals

This scenario tests real-world accessibility knowledge:

Interviewer: "How do you make a modal dialog accessible?"

The Bug (Inaccessible Modal)

<!-- BAD: Typical inaccessible modal -->
<div class="modal" style="display: block;">
  <div class="modal-content">
    <span class="close" onclick="closeModal()">×</span>
    <h2>Modal Title</h2>
    <p>Modal content</p>
  </div>
</div>

Problems:

  1. Focus doesn't move to modal when it opens
  2. User can tab to elements behind the modal
  3. Escape key doesn't close it
  4. Screen reader doesn't know it's a dialog
  5. Close button isn't keyboard accessible

The Solution (Accessible Modal)

<!-- GOOD: Accessible modal -->
<div
  role="dialog"
  aria-modal="true"
  aria-labelledby="modal-title"
  aria-describedby="modal-desc"
>
  <h2 id="modal-title">Modal Title</h2>
  <p id="modal-desc">Modal content goes here.</p>
 
  <button onclick="closeModal()">Close</button>
</div>
// Focus management
function openModal() {
  const modal = document.querySelector('[role="dialog"]');
  const firstFocusable = modal.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
 
  // Store the element that opened the modal
  previouslyFocused = document.activeElement;
 
  // Show modal and move focus
  modal.style.display = 'block';
  firstFocusable.focus();
 
  // Trap focus inside modal
  modal.addEventListener('keydown', trapFocus);
 
  // Close on Escape
  document.addEventListener('keydown', handleEscape);
}
 
function closeModal() {
  const modal = document.querySelector('[role="dialog"]');
  modal.style.display = 'none';
 
  // Return focus to trigger element
  previouslyFocused.focus();
 
  // Clean up listeners
  modal.removeEventListener('keydown', trapFocus);
  document.removeEventListener('keydown', handleEscape);
}
 
function trapFocus(e) {
  if (e.key !== 'Tab') return;
 
  const modal = e.currentTarget;
  const focusable = modal.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
  const first = focusable[0];
  const last = focusable[focusable.length - 1];
 
  if (e.shiftKey && document.activeElement === first) {
    e.preventDefault();
    last.focus();
  } else if (!e.shiftKey && document.activeElement === last) {
    e.preventDefault();
    first.focus();
  }
}
 
function handleEscape(e) {
  if (e.key === 'Escape') closeModal();
}

The lesson: "Accessible modals require focus management - move focus in, trap it inside, return it when closed, and support Escape to close. This is where many developers fail accessibility."

ARIA: The Double-Edged Sword

ARIA can help or hurt accessibility. Know the rules.

First Rule of ARIA: Don't Use ARIA

<!-- DON'T: Using ARIA when HTML works -->
<div role="button" tabindex="0">Click me</div>
 
<!-- DO: Use native HTML -->
<button>Click me</button>
 
<!-- DON'T: ARIA label on element with visible text -->
<button aria-label="Submit form">Submit</button>
 
<!-- DO: Just use the visible text -->
<button>Submit form</button>

When ARIA Is Necessary

<!-- Icon-only button needs label -->
<button aria-label="Search">
  <svg aria-hidden="true"><!-- magnifying glass --></svg>
</button>
 
<!-- Custom tabs widget -->
<div role="tablist" aria-label="Product information">
  <button role="tab" aria-selected="true" aria-controls="panel-1">
    Description
  </button>
  <button role="tab" aria-selected="false" aria-controls="panel-2">
    Reviews
  </button>
</div>
<div role="tabpanel" id="panel-1">Description content</div>
<div role="tabpanel" id="panel-2" hidden>Reviews content</div>
 
<!-- Live region for dynamic updates -->
<div aria-live="polite" aria-atomic="true">
  <!-- Screen reader announces when content changes -->
  Item added to cart
</div>
 
<!-- Loading state -->
<button aria-busy="true" disabled>
  <span aria-hidden="true">Loading...</span>
  <span class="sr-only">Please wait, submitting form</span>
</button>

Common ARIA Attributes

<!-- aria-label: Accessible name when no visible text -->
<button aria-label="Close">×</button>
 
<!-- aria-labelledby: Reference another element as label -->
<dialog aria-labelledby="dialog-title">
  <h2 id="dialog-title">Confirm Delete</h2>
</dialog>
 
<!-- aria-describedby: Additional description -->
<input type="email" aria-describedby="email-hint">
<p id="email-hint">We'll never share your email</p>
 
<!-- aria-expanded: Toggle state -->
<button aria-expanded="false" aria-controls="menu">Menu</button>
<ul id="menu" hidden>...</ul>
 
<!-- aria-hidden: Hide from screen readers -->
<span aria-hidden="true">★★★☆☆</span>
<span class="sr-only">3 out of 5 stars</span>
 
<!-- aria-live: Announce dynamic changes -->
<div aria-live="assertive">Error: Invalid email</div>
<div aria-live="polite">3 items in cart</div>

Keyboard Navigation Patterns

Skip Links

<!-- First element in body - skip to main content -->
<a href="#main-content" class="skip-link">
  Skip to main content
</a>
 
<header>
  <!-- Long navigation menu -->
</header>
 
<main id="main-content" tabindex="-1">
  <!-- tabindex="-1" allows programmatic focus -->
</main>
 
<style>
.skip-link {
  position: absolute;
  top: -40px;
  left: 0;
  padding: 8px;
  background: #000;
  color: #fff;
  z-index: 100;
}
 
.skip-link:focus {
  top: 0;  /* Visible when focused */
}
</style>

Focus Indicators

/* BAD: Removing focus indicator */
*:focus {
  outline: none;  /* NEVER do this without replacement */
}
 
/* GOOD: Custom focus indicator */
*:focus {
  outline: 2px solid #005fcc;
  outline-offset: 2px;
}
 
/* BETTER: Only for keyboard users */
*:focus:not(:focus-visible) {
  outline: none;
}
 
*:focus-visible {
  outline: 2px solid #005fcc;
  outline-offset: 2px;
}

Expected Keyboard Behaviors

// Buttons: Enter and Space
button.addEventListener('keydown', (e) => {
  if (e.key === 'Enter' || e.key === ' ') {
    e.preventDefault();
    button.click();
  }
});
 
// Menus: Arrow keys
menu.addEventListener('keydown', (e) => {
  const items = menu.querySelectorAll('[role="menuitem"]');
  const current = Array.from(items).indexOf(document.activeElement);
 
  switch (e.key) {
    case 'ArrowDown':
      e.preventDefault();
      items[(current + 1) % items.length].focus();
      break;
    case 'ArrowUp':
      e.preventDefault();
      items[(current - 1 + items.length) % items.length].focus();
      break;
    case 'Home':
      e.preventDefault();
      items[0].focus();
      break;
    case 'End':
      e.preventDefault();
      items[items.length - 1].focus();
      break;
    case 'Escape':
      closeMenu();
      break;
  }
});

Form Accessibility

Proper Form Labeling

<!-- BAD: No label association -->
<input type="email" placeholder="Email">
 
<!-- GOOD: Explicit label -->
<label for="email">Email address</label>
<input type="email" id="email" name="email">
 
<!-- ALSO GOOD: Implicit label -->
<label>
  Email address
  <input type="email" name="email">
</label>
 
<!-- Error messages -->
<label for="password">Password</label>
<input
  type="password"
  id="password"
  aria-describedby="password-error password-requirements"
  aria-invalid="true"
>
<p id="password-requirements">Must be at least 8 characters</p>
<p id="password-error" role="alert">Password is too short</p>
 
<!-- Required fields -->
<label for="name">
  Name <span aria-hidden="true">*</span>
</label>
<input type="text" id="name" required aria-required="true">

Fieldset for Related Inputs

<!-- Group related inputs -->
<fieldset>
  <legend>Shipping Address</legend>
 
  <label for="street">Street</label>
  <input type="text" id="street" name="street">
 
  <label for="city">City</label>
  <input type="text" id="city" name="city">
</fieldset>
 
<!-- Radio buttons always need fieldset -->
<fieldset>
  <legend>Preferred contact method</legend>
 
  <input type="radio" id="contact-email" name="contact" value="email">
  <label for="contact-email">Email</label>
 
  <input type="radio" id="contact-phone" name="contact" value="phone">
  <label for="contact-phone">Phone</label>
</fieldset>

Common Follow-Up Questions

"What's the difference between aria-label, aria-labelledby, and aria-describedby?"

"aria-label provides text directly as the accessible name - use it when there's no visible text.

aria-labelledby references another element's ID whose text becomes the accessible name - use it when visible text exists elsewhere.

aria-describedby references additional descriptive text that supplements the name - like hints or error messages. It's read after the name and role.

Priority: aria-labelledby > aria-label > native text content."

"How do you test for accessibility?"

"I use a combination:

  1. Automated tools - axe DevTools, Lighthouse, WAVE for quick scans
  2. Keyboard testing - Navigate entire site with Tab, Enter, Space, arrows
  3. Screen reader testing - VoiceOver (Mac), NVDA (Windows), or ChromeVox
  4. Zoom testing - 200% zoom, text-only zoom
  5. Color contrast checkers - WebAIM contrast checker

Automated tools catch only 30-40% of issues. Manual testing is essential."

"What is WCAG Level AA and what does it require?"

"WCAG AA is the standard compliance target. Key requirements:

  • Color contrast: 4.5:1 for normal text, 3:1 for large text
  • Resize: Content works at 200% zoom
  • Keyboard: All functionality via keyboard
  • Focus visible: Clear focus indicators
  • Headings: Logical heading structure
  • Forms: Labels, error identification, suggestions
  • Timing: Adjustable time limits
  • Navigation: Multiple ways to find pages, consistent navigation

Level A is minimum (like alt text), AAA is ideal (like sign language for video)."

"How do you handle dynamic content for screen readers?"

"Use ARIA live regions. aria-live='polite' announces when the user is idle - good for status updates. aria-live='assertive' interrupts immediately - use for errors.

Add aria-atomic='true' if the entire region should be re-read on any change."

<!-- Status messages -->
<div aria-live="polite">
  {{ cartItemCount }} items in cart
</div>
 
<!-- Error messages -->
<div aria-live="assertive" role="alert">
  Form submission failed: {{ errorMessage }}
</div>

What Interviewers Are Really Testing

When I ask about accessibility, I'm checking:

  1. Awareness - Do you consider users with disabilities?
  2. Semantic HTML - Do you know why it matters?
  3. ARIA knowledge - Do you know when to use it (and when not to)?
  4. Keyboard navigation - Can you implement proper focus management?
  5. Testing - Do you know how to verify accessibility?

A candidate who emphasizes semantic HTML first, knows the first rule of ARIA, and mentions testing with screen readers will stand out.

Quick Reference Card

TaskSolution
Make element focusableNative element or tabindex="0"
Hide from screen readersaria-hidden="true"
Hide visually but keep accessible.sr-only CSS class
Label icon buttonaria-label="Action name"
Announce dynamic contentaria-live="polite" or "assertive"
Required fieldrequired + aria-required="true"
Error statearia-invalid="true" + aria-describedby
Expanded/collapsedaria-expanded="true/false"
Group related inputs<fieldset> + <legend>
Color contrast (AA)4.5:1 normal text, 3:1 large text

Screen Reader Only CSS

/* Hide visually but keep accessible */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
 
/* Allow element to be focusable when navigated to */
.sr-only-focusable:focus {
  position: static;
  width: auto;
  height: auto;
  margin: 0;
  overflow: visible;
  clip: auto;
  white-space: normal;
}

Practice Questions

Test yourself before your interview:

1. What's wrong with this code?

<div onclick="submitForm()" class="btn-primary">Submit</div>

2. How would you make this image accessible?

<img src="company-org-chart.png" />

3. Write accessible HTML for a dropdown menu with a toggle button.

4. What does this do and when would you use it?

<div aria-live="polite" aria-atomic="true"></div>

Answers:

  1. Multiple issues: Not focusable, not keyboard accessible, not announced as button. Fix: Use <button type="submit">Submit</button>.

  2. Depends on context. If informative: alt="Organization chart showing CEO at top, 3 VPs reporting to CEO, and 2 managers under each VP". If decorative: alt="". If complex, add aria-describedby linking to a detailed text description.

<div class="dropdown">
  <button
    aria-expanded="false"
    aria-haspopup="true"
    aria-controls="dropdown-menu"
  >
    Options
  </button>
  <ul id="dropdown-menu" role="menu" hidden>
    <li role="menuitem"><a href="/edit">Edit</a></li>
    <li role="menuitem"><a href="/delete">Delete</a></li>
  </ul>
</div>
  1. It's a live region that announces content changes to screen readers. polite means it waits for idle time, atomic means the entire content is re-read on any change. Use for status updates like "3 items in cart" or "Changes saved".


Related Articles

If you found this helpful, check out these related guides:

Ready for More HTML5 Interview Questions?

This is just one topic from our complete HTML5 interview prep guide. Get access to 50+ HTML5 questions covering:

  • Semantic elements and document structure
  • Forms and validation
  • Web Storage and IndexedDB
  • Canvas and SVG
  • Web Workers and Service Workers

Get Full Access to All HTML5 Questions →

Or try our free HTML5 preview to see more questions like this.


Written by the EasyInterview team, based on real interview experience from 12+ years in tech and hundreds of technical interviews conducted at companies like BNY Mellon, UBS, and leading fintech firms.

Ready to ace your interview?

Get 550+ interview questions with detailed answers in our comprehensive PDF guides.

View PDF Guides