Claude Skills Guide

Chrome Extension Accessibility Audit: A Practical Guide

Accessibility in Chrome extensions often receives less attention than web applications, yet millions of users depend on assistive technologies to interact with browser extensions. Whether you are maintaining an existing extension or reviewing one you just shipped, conducting regular accessibility audits ensures your extension serves all users effectively—including those using screen readers, keyboard navigation, or magnification tools.

This guide walks you through the complete process of auditing a Chrome extension for accessibility issues, from automated scanning to manual testing with real assistive technologies. If you are starting from zero and want to build an AI-powered accessibility extension rather than audit an existing one, see the companion guide on building an AI accessibility Chrome extension.

Why Chrome Extension Accessibility Matters

Chrome extensions operate within a unique environment: they combine popup interfaces, options pages, content scripts injected into web pages, and background service workers. Each component presents distinct accessibility challenges. A popup with poor keyboard navigation prevents power users from efficiently using your extension. A content script that disrupts screen reader announcements creates confusion for users with visual impairments.

The Chrome Web Store policies explicitly require compliance with accessibility standards. Extensions that fail to meet basic accessibility requirements may face rejection during review or removal from the store.

Setting Up Your Audit Environment

Before beginning the audit, install the necessary tools. The Chrome Accessibility Developer Tools extension provides a solid foundation for automated checks:

# Install axe-core for command-line auditing
npm install -g axe-cli

# Audit a local extension unpacked directory
axe https://your-extension-url

For local development, the Lighthouse accessibility audit integrated into Chrome DevTools offers quick feedback. Open DevTools (F12), navigate to the Lighthouse tab, and run an accessibility audit on your extension’s popup or options page.

Automated Testing Tools

Axe Core Integration

Axe-core provides the most comprehensive automated testing for accessibility violations. Create a simple test script to audit your extension pages:

const AxeBuilder = require('@axe-core/playwright').default;
const { chromium } = require('playwright');

async function auditExtension() {
  const browser = await chromium.launch();
  const context = await browser.newContext();
  
  // Load your extension
  await context.addExtensions([pathToYourUnpackedExtension]);
  const page = await context.newPage();
  
  // Audit popup
  await page.goto('chrome-extension://YOUR_EXTENSION_ID/popup.html');
  const results = await new AxeBuilder({ page }).analyze();
  
  console.log(`Violations found: ${results.violations.length}`);
  results.violations.forEach(v => {
    console.log(`- ${v.id}: ${v.description}`);
  });
  
  await browser.close();
}

Run this script against all extension pages: popup, options page, any injected modals, and background management interfaces.

Testing Content Scripts

Content scripts run within web pages, inheriting their accessibility challenges. Test your content scripts against pages with varying accessibility issues:

// Test content script accessibility in context
async function testContentScriptAccessibility() {
  const testPages = [
    'https://example.com/form-heavy-page',
    'https://example.com/dynamic-content',
    'https://example.com/single-page-app'
  ];
  
  for (const url of testPages) {
    const results = await axe.analyze(`#your-extension-root-element`);
    // Check if your injected UI integrates properly
    // with existing page accessibility tree
  }
}

Manual Keyboard Navigation Testing

Automated tools catch approximately 30-40% of accessibility issues. Manual testing remains essential.

Testing Protocol

  1. Disable your mouse and attempt to complete every action using only keyboard
  2. Tab through all interactive elements in your popup and options page
  3. Verify focus indicators are visible on every focusable element
  4. Test keyboard traps — ensure focus can always escape modal dialogs
  5. Check focus management — when opening popups or dialogs, focus should move appropriately

Common keyboard navigation failures in Chrome extensions include:

Screen Reader Compatibility

Test your extension with actual screen readers. NVDA (Windows), VoiceOver (macOS), and TalkBack (Android) each present content differently.

Key Screen Reader Testing Steps

  1. Navigate using screen reader commands — not just Tab key
  2. Verify ARIA labels accurately describe interactive elements
  3. Check dynamic content announcements — does your extension announce updates properly?
  4. Test in multiple browsers — screen reader behavior varies between Chrome and Firefox

Example of proper ARIA implementation for an extension button:

<button 
  id="sync-data"
  aria-label="Synchronize data"
  aria-describedby="sync-status"
  aria-busy="false">
  <span aria-hidden="true"></span>
  <span class="sr-only">Synchronize data</span>
</button>

<div id="sync-status" class="sr-only">
  Last synced: 2 minutes ago
</div>

The .sr-only class (screen-reader only) hides visual text while making it available to assistive technologies:

.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;
}

Color Contrast and Visual Accessibility

Chrome extensions must meet WCAG 2.1 AA contrast ratios (4.5:1 for normal text, 3:1 for large text). Test your extension’s color scheme in:

The Chrome DevTools Color Picker includes a contrast ratio checker. Select any text element and verify it passes contrast requirements against its background.

Documenting and Fixing Issues

Create an accessibility audit report tracking:

Issue WCAG Criterion Severity Status
Missing focus styles 2.4.7 High Open
Empty link text 2.4.4 Critical Fixed
Low contrast 1.4.3 Medium Open

Prioritize fixes using severity: critical issues prevent any user with disabilities from using a feature, while minor issues cause inconvenience but do not block functionality.

Building a Custom Accessibility Checker Extension

If you want to embed accessibility scanning directly into your own extension rather than relying on external tools, the following patterns provide a solid starting point.

Manifest Setup

{
  "manifest_version": 3,
  "name": "Accessibility Tester",
  "version": "1.0",
  "description": "Automated accessibility testing for any webpage",
  "permissions": ["activeTab", "scripting"],
  "host_permissions": ["<all_urls>"],
  "action": {
    "default_popup": "popup.html",
    "default_icon": "icon.png"
  },
  "content_scripts": [{
    "matches": ["<all_urls>"],
    "js": ["content.js"]
  }]
}

Page Analysis: Images, Headings, and Form Labels

Inject a content script to detect the most common issues: missing image alt text, skipped heading levels, and unlabelled form inputs.

// content.js - Analyze page accessibility
function analyzeAccessibility() {
  const issues = [];

  // Check for images missing alt text
  const images = document.querySelectorAll('img');
  images.forEach((img) => {
    if (!img.hasAttribute('alt') && img.src) {
      issues.push({
        type: 'missing-alt',
        element: 'img',
        selector: getSelector(img),
        message: 'Image missing alternative text'
      });
    }
  });

  // Check heading structure
  const headings = document.querySelectorAll('h1, h2, h3, h4, h5, h6');
  let lastLevel = 0;
  headings.forEach(heading => {
    const currentLevel = parseInt(heading.tagName.charAt(1));
    if (currentLevel > lastLevel + 1 && lastLevel !== 0) {
      issues.push({
        type: 'heading-skip',
        element: heading.tagName,
        message: `Heading level skipped: ${lastLevel} to ${currentLevel}`
      });
    }
    lastLevel = currentLevel;
  });

  // Check form labels
  const inputs = document.querySelectorAll('input:not([type="hidden"]):not([type="submit"])');
  inputs.forEach(input => {
    const id = input.getAttribute('id');
    const labelled = document.querySelector(`label[for="${id}"]`);
    const parentLabel = input.closest('label');
    if (!labelled && !parentLabel) {
      issues.push({
        type: 'missing-label',
        element: 'input',
        selector: getSelector(input),
        message: 'Form input missing associated label'
      });
    }
  });

  return issues;
}

function getSelector(el) {
  if (el.id) return `#${el.id}`;
  let selector = el.tagName.toLowerCase();
  if (el.className) {
    selector += '.' + el.className.split(' ')[0];
  }
  return selector;
}

Programmatic Color Contrast Calculation

To check contrast ratios at runtime rather than relying on DevTools, compute relative luminance directly from computed styles:

function getContrastRatio(foreground, background) {
  const lum1 = getLuminance(foreground);
  const lum2 = getLuminance(background);
  const brightest = Math.max(lum1, lum2);
  const darkest = Math.min(lum1, lum2);
  return (brightest + 0.05) / (darkest + 0.05);
}

function getLuminance(hex) {
  const rgb = hexToRgb(hex);
  const [r, g, b] = rgb.map(c => {
    c = c / 255;
    return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
  });
  return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}

function hexToRgb(hex) {
  const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  return result ? [
    parseInt(result[1], 16),
    parseInt(result[2], 16),
    parseInt(result[3], 16)
  ] : [0, 0, 0];
}

WCAG 2.1 AA requires 4.5:1 for normal text and 3:1 for large text. Call getContrastRatio with the foreground and background hex values extracted from window.getComputedStyle.

Programmatic Keyboard Accessibility Checks

Beyond manual testing, you can detect two common keyboard issues in code: positive tabindex values (which disrupt natural tab order) and focusable elements with no visible focus ring.

function checkKeyboardAccessibility() {
  const issues = [];

  // Check for positive tabindex
  const positiveTabindex = document.querySelectorAll('[tabindex="1"], [tabindex="2"], [tabindex="3"]');
  positiveTabindex.forEach(() => {
    issues.push({
      type: 'positive-tabindex',
      message: 'Avoid positive tabindex values; use 0 or -1'
    });
  });

  // Check for focusable elements without visible focus
  const focusableSelectors = 'a[href], button, input, select, textarea, [tabindex]';
  const focusableElements = document.querySelectorAll(focusableSelectors);

  focusableElements.forEach(el => {
    const style = window.getComputedStyle(el);
    if (style.outline === 'none' || style.outlineColor === 'transparent') {
      issues.push({
        type: 'missing-focus-style',
        message: 'Focusable element lacks visible focus indicator'
      });
    }
  });

  return issues;
}

ARIA Validation

Catch empty or invalid ARIA roles and mismatched aria-required usage:

function validateARIA() {
  const issues = [];

  // Check for invalid ARIA roles
  const invalidRoles = document.querySelectorAll('[role=""], [role="null"]');
  invalidRoles.forEach(() => {
    issues.push({
      type: 'invalid-aria-role',
      message: 'Element has empty or invalid ARIA role'
    });
  });

  // Check for required ARIA properties
  const ariaRequired = document.querySelectorAll('[aria-required="true"]');
  ariaRequired.forEach(el => {
    if (!el.hasAttribute('required') && el.tagName !== 'INPUT' && el.tagName !== 'SELECT') {
      issues.push({
        type: 'aria-required-check',
        message: 'Element has aria-required but missing native required'
      });
    }
  });

  return issues;
}

Displaying Results in the Extension Popup

Wire up a minimal popup to display findings from the content script:

<!-- popup.html -->
<!DOCTYPE html>
<html>
<head>
  <style>
    body { width: 350px; padding: 16px; font-family: system-ui, sans-serif; }
    .issue { padding: 8px; margin: 4px 0; border-radius: 4px; background: #fee; }
    .issue-type { font-weight: bold; color: #c00; }
    .issue-message { font-size: 13px; color: #333; }
    .summary { padding: 12px; background: #eef; border-radius: 4px; margin-bottom: 12px; }
  </style>
</head>
<body>
  <h2>Accessibility Report</h2>
  <div id="summary" class="summary"></div>
  <div id="issues"></div>
  <script src="popup.js"></script>
</body>
</html>
// popup.js
document.addEventListener('DOMContentLoaded', () => {
  chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
    chrome.tabs.sendMessage(tabs[0].id, { action: 'analyze' }, (response) => {
      if (response && response.issues) {
        displayResults(response.issues);
      }
    });
  });
});

function displayResults(issues) {
  const summary = document.getElementById('summary');
  const container = document.getElementById('issues');

  summary.textContent = `Found ${issues.length} accessibility issue${issues.length !== 1 ? 's' : ''}`;

  issues.forEach(issue => {
    const div = document.createElement('div');
    div.className = 'issue';
    div.innerHTML = `
      <div class="issue-type">${issue.type}</div>
      <div class="issue-message">${issue.message}</div>
    `;
    container.appendChild(div);
  });
}

Best Practices for Accessibility-Checker Extensions

Continuous Accessibility Testing

Integrate accessibility testing into your development workflow:

// In your test suite
import { A11yAudit } from './a11y-audit';

describe('Extension popup accessibility', () => {
  it('should have no critical accessibility violations', async () => {
    const results = await A11yAudit.analyzePage('popup.html');
    const critical = results.violations.filter(v => v.impact === 'critical');
    expect(critical).toHaveLength(0);
  });
});

Run these tests in CI to catch regressions before shipping updates.

Conclusion

Regular accessibility audits protect all users—particularly those relying on assistive technologies. By combining automated scanning with manual testing and integrating accessibility checks into your development workflow, you build extensions that work effectively for everyone.

The effort required for accessibility auditing is modest compared to the impact: inclusive extensions reach more users and comply with store policies. Start with the automated tools, add keyboard and screen reader testing, and maintain accessibility as a continuous priority.

Built by theluckystrike — More at zovo.one