Skip to main content
Reachablr

Shifting Accessibility Left: Catching Issues in PRs Instead of Post-Launch

7 min read
Shifting Accessibility Left: Catching Issues in PRs Instead of Post-Launch

In software development, "shifting left" means moving quality checks earlier in the process — closer to when code is written, rather than after it is deployed. The term comes from visualizing the development lifecycle as a timeline running left to right, with production on the far right. Moving checks to the left means catching problems before they are expensive to fix.

Accessibility works the same way, but most teams still treat it as a post-launch concern. An accessibility audit runs after deployment, finds violations, and generates a remediation backlog that competes with new feature work. The cycle repeats on the next release.

Shift-left accessibility breaks that cycle by integrating WCAG checks into the development workflow itself.

Why Catching Issues Late Is Expensive

The economics of accessibility remediation follow the same pattern as other software defects: the earlier you find it, the cheaper it is to fix.

A missing aria-label on a button caught during code review takes a developer 30 seconds to fix. The same issue found in a post-deployment audit requires triaging the bug report, assigning it to a sprint, a developer context-switching back to code they may not have touched in weeks, testing the fix, and deploying it through the full release pipeline. The estimate typically cited in software literature is a 5-10x cost multiplier for defects found in production versus defects found during development.

For accessibility specifically, the multiplier can be higher. Many accessibility issues are not isolated to a single element — they reflect a systematic pattern across a component library or design system. Fixing them post-launch means touching every instance across every page, with the risk of introducing new issues in each change.

Shift-left accessibility does not eliminate audits. It reduces what audits find.

Lint-Time: The First Line of Defense

The cheapest place to catch an accessibility issue is before the code is committed.

eslint-plugin-jsx-a11y is a static analysis plugin for ESLint that checks JSX for common accessibility mistakes as you write. It catches:

  • Images without alt attributes
  • Anchor tags (<a>) with no accessible text (no children, no aria-label)
  • Interactive elements built from non-semantic HTML with no ARIA role (<div onClick> without role="button")
  • Form inputs without associated labels
  • ARIA attributes with invalid values or used on inappropriate element types

This plugin ships as part of the default Next.js ESLint configuration. If you are using Next.js, it is already running. The rules catch violations in the editor (if you have ESLint integration in your IDE) and in the pre-commit lint step.

Lint-time checks are fast — sub-second per file — and completely blocking. A lint failure in CI means the violation never reaches a pull request review.

Unit Test Level: jest-axe and @testing-library/react

Static analysis cannot catch runtime accessibility issues — violations that depend on how a component renders with real props and state. For those, you need rendered component testing.

jest-axe is a wrapper around axe-core (the accessibility testing engine) that integrates with Jest. Combined with @testing-library/react, it renders a component into a virtual DOM and runs the full axe rule set against the rendered HTML.

A basic test looks like this:

import { render } from '@testing-library/react'
import { axe, toHaveNoViolations } from 'jest-axe'
expect.extend(toHaveNoViolations)

test('Button has no accessibility violations', async () => {
  const { container } = render(<Button>Submit form</Button>)
  const results = await axe(container)
  expect(results).toHaveNoViolations()
})

axe-core catches violations that ESLint cannot — particularly:

  • Heading order violations (h1 → h3, skipping h2)
  • Color contrast failures computed from rendered CSS
  • ARIA landmark structure issues (multiple main elements, missing nav landmark)
  • Form labels that are technically present but not correctly associated with their inputs

Running axe-core in unit tests gives you accessibility coverage at the component boundary, before any integration. Every new component and every modified component can have its own axe assertion — the test suite becomes a regression gate that prevents known-passing components from regressing.

Pull Request Scanning: Block Merges on New Violations

Unit tests cover individual components. They miss violations that only appear when components are assembled into pages — navigation items that conflict with the page's h1, a sticky header that obscures a focused element, or a modal that traps focus without an escape mechanism.

Pull request scanning solves this by running accessibility checks against assembled, rendered pages in a CI environment on every PR.

The workflow looks like this:

  1. Developer opens a pull request.
  2. CI builds the application and starts a preview server.
  3. An accessibility scanner runs axe-core (or equivalent) against a list of target URLs on the preview server.
  4. If new violations are detected that were not present on the base branch, the CI check fails and the PR cannot merge.
  5. The violation report is posted as a PR comment showing exactly which rule failed, on which URL, and on which element.

New violations only is the key design choice. Scanning against a baseline (the current main branch) means you are not blocked by pre-existing accessibility debt — only net-new violations introduced by the PR. This prevents the "it was already broken" situation from paralyzing the merge queue.

Reachablr's PR Scan capability is built for this workflow. It runs against your preview or staging environment on every pull request, compares against the base branch baseline, and reports only the violations your PR introduced. The result: accessibility becomes a pass/fail gate in the same pull request review process that enforces test coverage and code style.

Code Scanning: Static Analysis on the Repository

Some accessibility violations are not detectable from rendered output. Component code can contain structural patterns — aria-hidden on an element that contains focusable children, an onClick handler without a corresponding onKeyDown, a custom select component with no keyboard navigation logic — that only become violations in specific render contexts.

Static code analysis on the repository (not just the running app) can surface these patterns. The scanner reads component source files, identifies accessibility anti-patterns in the component logic, and reports findings against specific lines of code rather than rendered URLs.

This is distinct from ESLint in that it operates on the compiled component graph, not just the individual file. A scanner can detect that a component imports an inner component that has a known ARIA issue, even if the outer component passes lint checks.

Reachablr's Code Scan runs this analysis on your repository at commit or on a schedule, giving you a view of accessibility issues in component code that URL scanning cannot reach — particularly useful for components that are only visible in certain states (empty states, error states, loading states) that may not be exercised in automated URL scanning.

Getting Started

Implementing shift-left accessibility does not require adopting everything at once. A pragmatic sequence:

  1. Enable eslint-plugin-jsx-a11y in your ESLint config and run it in CI. Fix existing lint violations to establish a clean baseline.
  2. Add jest-axe assertions to your component test suite, starting with components that appear on every page.
  3. Integrate PR-level scanning against your preview deployments. Set a baseline from main so existing debt does not block development — only net-new violations block merges.
  4. Run quarterly manual audits with a screen reader. Automated tools catch 30-40% of WCAG violations; human judgment covers the rest.

Violations stop making it to production because the pipeline blocks them before they ship.

Scan Your Site for Free

Reachablr scans live URLs, analyzes your source code, checks every pull request, and auto-fixes React and JavaScript — WCAG 2.1 AA coverage across your entire development workflow.

Get Started Free