Design11 min read

Extension Accessibility Checklist

Complete WCAG 2.1 AA accessibility checklist for Chrome extensions — keyboard navigation, screen readers, contrast, ARIA, focus management, and testing with assistive technologies.

C
CWS Kit Team
Share

Extension Accessibility Checklist

WCAG 2.1 AA compliance for every extension surface — popup, options page, side panel, and content scripts.

Accessibility is not a feature. It is a quality bar. An extension that 15% of users cannot operate — because they use a screen reader, navigate by keyboard, have low vision, or experience motor limitations — is a broken extension that happens to work for the majority.

Chrome extensions present unique accessibility challenges that web applications do not. Popups are small, ephemeral surfaces where focus management matters enormously. Options pages are forms that must work without a mouse. Content scripts inject UI into pages with unpredictable DOM structures and styling. Side panels maintain persistent state while the user interacts with a completely different page.

This guide is a working checklist. Print it, bookmark it, or integrate it into your pull request template. Every item is actionable and testable.

15-20%

Users with Disabilities

WHO estimate of global population with some form of disability

~7%

Keyboard-Only Users

Including power users who prefer keyboard navigation

~2%

Screen Reader Users

Using JAWS, NVDA, VoiceOver, or ChromeVox

~8% of men

Color Vision Deficiency

Red-green deficiency most common; affects UI color choices

Accessibility Audit Process#

Before diving into individual checklist items, understand the systematic process for auditing your extension.

1

Automated Scan

Run axe DevTools or Lighthouse accessibility audit on every extension page (popup, options, side panel). Automated tools catch about 30% of accessibility issues — things like missing alt text, insufficient contrast, and missing ARIA labels.

2

Keyboard Navigation Test

Unplug your mouse. Navigate your entire extension using only Tab, Shift+Tab, Enter, Space, Arrow keys, and Escape. Every interactive element must be reachable and operable. Focus order must be logical.

3

Screen Reader Walkthrough

Turn on ChromeVox (built into Chrome OS) or VoiceOver (macOS). Navigate your extension as a screen reader user would. Listen to what is announced. Does it make sense without seeing the screen?

4

Zoom and Reflow Test

Zoom to 200% in your popup and options page. Does content reflow properly? Is anything cut off or overlapping? WCAG requires content to be usable at 200% zoom.

5

Color-Only Information Test

View your extension in grayscale (use a CSS filter or OS accessibility setting). Can you still understand every piece of information? Status indicators, error states, and success messages must not rely solely on color.

6

Motion and Animation Test

Enable 'Reduce Motion' in your OS settings. Do all animations respect prefers-reduced-motion? Are there any auto-playing animations that could trigger vestibular disorders?

Checklist by Extension Context#

Different extension surfaces have different accessibility requirements. Use the appropriate section for each part of your UI.

The popup is a constrained environment — typically 300-400px wide and 500-600px tall. Focus management is critical because the popup appears and disappears frequently. **Focus requirements:** When the popup opens, focus should land on the first interactive element or a meaningful heading — not on a decorative element or the body. When the popup closes and reopens, focus should return to a sensible default, not remember the last focused element (which may no longer be relevant). **Keyboard trap prevention:** Every element in the popup must be escapable via keyboard. Modal dialogs within the popup need focus trapping (Tab cycles within the modal) with Escape to close. But the popup itself must never trap focus — the user should be able to Tab out to the browser chrome. **Compact layout considerations:** Small font sizes are tempting in popups to fit more content. But WCAG requires a minimum of 12px for body text, and 14px is the practical minimum for comfortable reading. Touch targets (for users with motor difficulties) should be at least 44x44px.

Screen Reader Compatibility#

Not all screen readers behave identically. The major ones have meaningful differences in how they interpret ARIA and handle dynamic content.

FeatureFeatureJAWS (Windows)NVDA (Windows)VoiceOver (macOS)ChromeVox
Market share~40%~30%~15%~5%
ARIA supportExcellentExcellentGoodGood
Shadow DOMPartialPartialGoodBest
Live regionsReliableReliableSometimes delayedReliable
Custom elementsNeeds ARIA rolesNeeds ARIA rolesBetter native supportBest support
Cost$1000+ licenseFree / open sourceFree (built-in)Free (built-in)

ChromeVox deserves special attention for Chrome extension testing because it is the screen reader most likely to be used inside Chrome. It understands Chrome-specific UI patterns better than other screen readers and handles Shadow DOM more consistently. Test with ChromeVox first, then verify with at least one other screen reader.

ARIA Patterns for Extensions#

Here are the ARIA patterns most commonly needed in Chrome extension UI.

Accessible Toggle Button#

// Toggle button with proper ARIA state
function createToggle(label: string, initialState: boolean): HTMLButtonElement {
  const button = document.createElement("button");
  button.setAttribute("role", "switch");
  button.setAttribute("aria-checked", String(initialState));
  button.setAttribute("aria-label", label);
  button.textContent = initialState ? "On" : "Off";
 
  button.addEventListener("click", () => {
    const current = button.getAttribute("aria-checked") === "true";
    const next = !current;
    button.setAttribute("aria-checked", String(next));
    button.textContent = next ? "On" : "Off";
  });
 
  // Keyboard: Space and Enter both toggle
  button.addEventListener("keydown", (e) => {
    if (e.key === " " || e.key === "Enter") {
      e.preventDefault();
      button.click();
    }
  });
 
  return button;
}

Live Status Region#

<!-- Announce dynamic status changes to screen readers -->
<div id="status" role="status" aria-live="polite" aria-atomic="true">
  <!-- Content updated dynamically -->
</div>
 
<script>
  function updateStatus(message: string): void {
    const status = document.getElementById("status")!;
    // Clear first, then set — ensures re-announcement of same message
    status.textContent = "";
    requestAnimationFrame(() => {
      status.textContent = message;
    });
  }
 
  // Usage:
  updateStatus("Settings saved successfully");
  updateStatus("3 items selected");
  updateStatus("Loading complete — 42 results found");
</script>

Focus Trap for Modal Dialogs#

function trapFocus(container: HTMLElement): () => void {
  const focusable = container.querySelectorAll<HTMLElement>(
    'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
  );
  const first = focusable[0];
  const last = focusable[focusable.length - 1];
 
  function handler(e: KeyboardEvent): void {
    if (e.key !== "Tab") return;
 
    if (e.shiftKey) {
      if (document.activeElement === first) {
        e.preventDefault();
        last.focus();
      }
    } else {
      if (document.activeElement === last) {
        e.preventDefault();
        first.focus();
      }
    }
  }
 
  container.addEventListener("keydown", handler);
  first.focus();
 
  // Return cleanup function
  return () => container.removeEventListener("keydown", handler);
}

Common Mistakes#

Do
  • Use semantic HTML elements (button, a, input) instead of div/span with click handlers
  • Provide text alternatives for every non-decorative image (alt text or aria-label)
  • Ensure focus indicators are visible — never use outline: none without a replacement
  • Test with actual screen readers, not just automated tools
  • Support both mouse and keyboard for every interaction
  • Use rem/em units for font sizes so they respect user zoom preferences
Avoid
  • Use div or span as buttons — screen readers announce them as generic text, not interactive elements
  • Hide content with display: none when it should be available to screen readers — use visually-hidden CSS instead
  • Use title attributes as the only accessible label — most screen readers ignore them
  • Rely on color alone to communicate state (red for error, green for success)
  • Use tabindex values greater than 0 — they create unpredictable tab ordering
  • Auto-focus elements on page load without user expectation — it disorients screen reader users

Compliance Progress Tracking#

Use these areas to track your extension's WCAG 2.1 AA compliance. Each area should be tested independently.

Perceivable (text alternatives, captions, contrast)100% target
Operable (keyboard, timing, navigation)100% target
Understandable (readable, predictable, input assistance)100% target
Robust (compatible with assistive technology)100% target

These four principles — Perceivable, Operable, Understandable, and Robust (POUR) — are the foundation of WCAG. Every specific criterion maps to one of these principles. When you are unsure whether something is an accessibility issue, ask: "Can all users perceive this? Operate it? Understand it? Access it with their assistive technology?"

Test Your Knowledge#

Knowledge Check

1. What is the minimum contrast ratio required by WCAG 2.1 AA for normal-sized text?

2. Why should you avoid using tabindex values greater than 0?

3. What happens with aria-labelledby references across Shadow DOM boundaries?

4. What is the minimum touch target size recommended by WCAG?

Automated Testing Integration#

Integrate accessibility checks into your build pipeline so regressions are caught before they ship.

// playwright.config.ts — add accessibility tests to E2E suite
import { test, expect } from "@playwright/test";
import AxeBuilder from "@axe-core/playwright";
 
test("popup meets WCAG 2.1 AA", async ({ page }) => {
  // Load the popup page
  await page.goto(`chrome-extension://${EXTENSION_ID}/popup.html`);
 
  // Run axe accessibility scan
  const results = await new AxeBuilder({ page })
    .withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"])
    .analyze();
 
  // Fail the test if any violations are found
  expect(results.violations).toEqual([]);
});
 
test("options page keyboard navigation", async ({ page }) => {
  await page.goto(`chrome-extension://${EXTENSION_ID}/options.html`);
 
  // Tab through all interactive elements
  const focusOrder: string[] = [];
  for (let i = 0; i < 20; i++) {
    await page.keyboard.press("Tab");
    const focused = await page.evaluate(() => {
      const el = document.activeElement;
      return el ? `${el.tagName.toLowerCase()}${el.id ? "#" + el.id : ""}` : "none";
    });
    focusOrder.push(focused);
    if (focused === "none" || focused === "body") break;
  }
 
  // Verify logical focus order (customize for your UI)
  expect(focusOrder[0]).toContain("input"); // Search field first
  expect(focusOrder).not.toContain("none"); // No focus traps
});

Checklist

  • Every interactive element is operable via keyboard (Tab, Enter, Space, Escape)
  • Focus order follows a logical sequence matching visual layout
  • Focus indicators are visible in both light and dark themes
  • All images have appropriate alt text (or alt='' for decorative images)
  • Color is never the sole means of conveying information
  • Text contrast meets 4.5:1 for normal text and 3:1 for large text
  • Form inputs have associated labels (not just placeholders)
  • Error messages are connected to inputs via aria-describedby
  • Dynamic content updates are announced via aria-live regions
  • Modal dialogs trap focus and return focus on close
  • The extension respects prefers-reduced-motion for animations
  • Content is usable at 200% browser zoom without horizontal scrolling
  • Screen reader testing completed with at least one screen reader
  • Automated accessibility tests integrated into CI pipeline

Accessibility is an ongoing practice, not a one-time audit. Every new feature, every UI change, and every bug fix should pass through these checks. For more on building polished extension interfaces, see implementing dark mode and extension branding guide.

Continue reading

Related articles

View all posts