← Back to Website builders

Skill · Version 1.0.0

Automated accessibility protection

Builds repeatable accessibility checks into shared components so later changes reveal broken names, keyboard interactions and focus behavior early.

accessibility-regression-automation

Team members using this skill

See how it helps

When is it useful?

Use it after fixing an accessibility problem or when a component library needs protection against the same barrier returning.

What does it help you do?

  • Tests controls by accessible role and name.
  • Checks dialog focus trapping, Escape and focus restoration.
  • Runs accessibility scans with open overlays and validation errors.

Try asking this

Protect our shared dialog against keyboard and missing-label regressions. Open it from a real trigger, submit an invalid form and close it with Escape. The test should detect lost focus or an unnamed control.

Detailed documentation

Read the skill’s original instructions, examples and checks in their authored language.

Details…Show less

Accessibility Regression Automation

Fixing an accessibility bug once in a template is fragile if the next feature change can silently remove an aria-label, break a modal focus trap or introduce a clickable <div>. Embed deterministic accessibility checks into the component library and automated test suite so regressions fail fast during development.

1. Query by Accessible Role & Name in Tests

Write component and E2E tests the same way a user and assistive technology interact with the UI (Testing Library / Playwright locator philosophy):

  • Prefer role + accessible name queries over CSS classes or data-testid:
    • Playwright: page.getByRole('button', { name: 'Create task' }), page.getByRole('dialog', { name: 'New project' }), page.getByLabel('Project name').
    • Testing Library: getByRole('tab', { name: 'Settings', selected: true }), getByRole('combobox', { name: 'Assignee' }).
  • Why this prevents regressions: if a button's accessible name breaks, its <label> becomes detached from its <input>, or a <button> is replaced with an unsemantic <div>, the functional test fails immediately.

2. Automated Keyboard & Focus Lifecycle Assertions

Do not test interactive components solely with .click() — add explicit keyboard and focus assertions for critical flows and shared primitives:

  • Dialog lifecycle test:
    1. Focus trigger button and press Enter.
    2. Assert expect(dialog).toBeVisible() and verify document.activeElement is inside the dialog.
    3. Press Tab through the dialog controls and verify focus wraps within the dialog without escaping to background elements.
    4. Press Escape, assert dialog is closed/hidden, and assert expect(triggerButton).toBeFocused().
  • Composite widget navigation test:
    • For tabs, menus and comboboxes, dispatch ArrowRight/ArrowDown, Home, End and Enter key events and assert both aria-selected/aria-expanded states and :focus target changes.
  • Form validation test:
    • Submit an invalid form via Enter and assert aria-invalid="true" on the field, that the error message is linked via aria-describedby, and that focus moves to the first invalid field or error summary.

3. Integrating axe-core Across Component States

Running an axe scan only on the initial page load misses 80% of interactive UI:

  • Run @axe-core/playwright (or vitest-axe / jest-axe) against each meaningful state in an E2E or component test:
    1. Initial page render.
    2. With dropdown menu or popover open.
    3. With modal dialog open.
    4. With form validation errors displayed.
    5. In both light and dark themes (where supported) to catch theme-specific contrast failures.
  • Scope scans to the opened overlay or component when testing isolated widgets, and never disable core axe rules (color-contrast, button-name, label, aria-valid-attr) globally to hide failures.

4. Static Linting & Shared Primitive Guardrails

  • Enable and respect static template/JSX accessibility lint rules (eslint-plugin-jsx-a11y, eslint-plugin-vuejs-accessibility, @angular-eslint/template/accessibility).
  • Enforce accessibility at the design-system primitive level: require accessible names or labels in component TypeScript types (e.g., an IconButton component whose props require ariaLabel: string at compile time so an unlabeled icon button cannot compile).

Included files 1

  • SKILL.md