Skip to content

Testing Components for Accessibility — Automated and Manual Testing

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Testing Components for Accessibility. We cover key concepts, practical examples, and best practices to help you master this topic.

Testing components for accessibility in design systems combines automated unit tests with axe-core, manual keyboard testing, screen reader verification, contrast validation, and visual regression testing for accessibility regressions.

What You'll Learn

You will learn how to set up accessibility testing in a design system, what types of tests to run, how to integrate testing into CI/CD, and how to document testing results.

Why It Matters

Testing at the design system level catches accessibility issues once instead of in every product that uses the component. A component that passes a11y tests can be trusted across all products.

Real-World Use

DodaKit runs automated accessibility tests in CI for every pull request. Tests include axe-core scanning, keyboard navigation checks, and contrast validation. Manual testing with NVDA is done before each release.

flowchart TD
  A[Testing] --> B[Automated]
  A --> C[Manual]
  A --> D[Screen Reader]
  A --> E[Regression]
  B --> B1[axe-core in CI]
  B --> B2[Contrast check]
  C --> C1[Keyboard only]
  C --> C2[Zoom to 400%]
  D --> D1[NVDA, VoiceOver]
  E --> E1[Percy, Chromatic]

Automated Testing

Use axe-core to automate accessibility checks. Run tests in unit tests, Storybook, or integration tests. Fail the build on critical and serious violations.

Keyboard Testing

Create automated keyboard tests that tab through all interactive elements and verify focus indicators are visible at each stop.

Contrast Validation

Check color contrast programmatically for all color tokens and component color pairs. Fail if any pair violates WCAG AA.

// Component accessibility test suite
class ComponentA11yTest {
  constructor(componentName) {
    this.componentName = componentName;
    this.results = [];
  }

  async runAxeCheck(html) {
    // Simulated axe-core check
    const violations = [];

    // Check for required ARIA attributes
    if (html.includes('role=')) {
      const required = ['aria-label', 'aria-labelledby', 'aria-describedby'];
      required.forEach(attr => {
        if (html.includes('role=') && !html.includes(attr)) {
          violations.push({
            id: 'aria-required-attr',
            impact: 'critical',
            description: `ARIA role requires ${attr}`
          });
        }
      });
    }

    // Check for focus-visible
    if (!html.includes(':focus-visible') && !html.includes('outline')) {
      violations.push({
        id: 'focus-visible',
        impact: 'serious',
        description: 'No visible focus indicator found'
      });
    }

    // Check for label-input association
    if (html.includes('type=') && !html.includes('aria-label') && !html.includes('<label')) {
      violations.push({
        id: 'label',
        impact: 'critical',
        description: 'Input missing accessible label'
      });
    }

    this.results.push({
      test: 'axe-check',
      violations: violations,
      passed: violations.length === 0
    });

    return { violations, passed: violations.length === 0 };
  }

  runKeyboardCheck(interactiveCount) {
    // Simulate tabbing through interactive elements
    const issues = [];
    const tabStops = [];

    for (let i = 0; i < interactiveCount; i++) {
      tabStops.push({ index: i, hasFocus: true });
    }

    if (tabStops.length === 0 && interactiveCount > 0) {
      issues.push('No tab stops found on interactive component');
    }

    this.results.push({
      test: 'keyboard',
      tabStops: tabStops.length,
      issues: issues,
      passed: issues.length === 0
    });

    return { tabStops: tabStops.length, issues, passed: issues.length === 0 };
  }

  getSummary() {
    const allPassed = this.results.every(r => r.passed);
    return {
      component: this.componentName,
      totalTests: this.results.length,
      passed: this.results.filter(r => r.passed).length,
      failed: this.results.filter(r => !r.passed).length,
      allPassed: allPassed,
      details: this.results.map(r => ({
        test: r.test,
        passed: r.passed,
        issues: r.test === 'axe-check' ? r.violations.length : r.issues.length
      }))
    };
  }
}

const test = new ComponentA11yTest('Dropdown');
const axeResult = await test.runAxeCheck('<div class="dropdown" role="listbox"><div role="option">Item</div></div>');
console.log('Axe:', axeResult);

const kbResult = test.runKeyboardCheck(3);
console.log('Keyboard:', kbResult);

console.log('Summary:', test.getSummary());

Expected output:

Axe: { violations: [ { id: 'aria-required-attr', impact: 'critical', description: 'ARIA role requires aria-label' } ], passed: false }
Keyboard: { tabStops: 3, issues: [], passed: true }
Summary: { component: 'Dropdown', totalTests: 2, passed: 1, failed: 1, allPassed: false, details: [ { test: 'axe-check', passed: false, issues: 1 }, { test: 'keyboard', passed: true, issues: 0 } ] }

Screen Reader Testing

Test each component with NVDA on Windows and VoiceOver on macOS. Verify that content and interactions are announced correctly.

Visual Regression

Use visual regression tools like Percy or Chromatic to catch visual changes that may affect accessibility, such as contrast changes or focus indicator removal.

<!-- Testing documentation for components -->
<section aria-label="Testing results for Button component">
  <h2>Button — Testing Results</h2>

  <h3>Automated (axe-core 4.8)</h3>
  <ul>
    <li>Critical violations: 0</li>
    <li>Serious violations: 0</li>
    <li>Moderate violations: 0</li>
    <li>Passed: Yes</li>
  </ul>

  <h3>Keyboard</h3>
  <ul>
    <li>Tab: Focuses button</li>
    <li>Enter/Space: Activates button</li>
    <li>Focus indicator visible: Yes</li>
    <li>Passed: Yes</li>
  </ul>

  <h3>Screen Reader (NVDA 2024 + Chrome 125)</h3>
  <ul>
    <li>Button announced correctly: Yes</li>
    <li>Disabled state announced: Yes</li>
    <li>Toggle state announced: Yes</li>
    <li>Passed: Yes</li>
  </ul>

  <h3>Contrast</h3>
  <ul>
    <li>Primary text on primary bg: 4.8:1 (Pass AA)</li>
    <li>Secondary text on surface: 5.7:1 (Pass AA)</li>
    <li>Disabled text on surface: 2.8:1 (Fail AA) — Known issue</li>
  </ul>
</section>

Common Mistakes

1. No Automated Accessibility Tests

Without automated tests, accessibility issues are only found during manual testing or in production.

2. Ignoring axe-Core Violations

Failing tests should block PRs. Allowing critical violations into the design system propagates inaccessibility.

3. No Keyboard Testing Automation

Automated keyboard tests catch focus order and visibility issues that axe-core does not.

4. Testing Only in One Browser

Accessibility varies by browser and assistive technology. Test in Chrome, Firefox, and Safari.

5. No Screen Reader Testing

Automated tests cannot verify screen reader output. Manual testing with NVDA and VoiceOver is essential.

6. No Visual Regression for a11y

A visual change that reduces contrast or removes a focus indicator may go unnoticed without visual regression tests.

7. No Accessibility Gate in CI

Accessibility tests must be part of CI. If they are optional, they will be skipped.

Practice Questions

1. What are the three types of accessibility testing for design system components?

Automated (axe-core), manual keyboard, and screen reader testing.

2. Why should axe-core tests run in CI?

To catch accessibility issues before components are released. Failing tests should block PRs.

3. What does visual regression testing catch for accessibility?

Visual changes that may reduce contrast, resize touch targets, or remove focus indicators.

4. Which screen readers should components be tested with?

NVDA on Windows and VoiceOver on macOS. JAWS for enterprise requirements.

5. Challenge: Set up an accessibility test plan for a design system with 5 components. Include automated, keyboard, screen reader, and contrast tests.

FAQ

Can I rely only on automated testing?

No. Automated testing catches about 30 percent of issues. Manual keyboard and screen reader testing are required.

How often should components be tested?

Test every component before release. Run automated tests in CI for every PR. Do a full manual pass before each release.

What is the best axe-core configuration for design systems?

Run all rules. Set critical and serious violations as blockers. Moderate and minor violations as warnings.

How do I test focus management automatically?

Use a script that tabs through all interactive elements and checks for visible focus indicators at each stop.

Should I test disabled states?

Yes. Disabled states often have lower contrast. Ensure disabled text meets 3:1 minimum.

Mini Project

Create an accessibility test suite for 3 design system components. Include automated axe-core checks, keyboard tab order tests, contrast validation, and documentation of screen reader behavior.

What's Next

Learn about Contribution Guidelines for keeping accessibility standards in design system contributions. Then complete the Design System Project.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro