# Review Pull Requests for Accessibility in a React App

Yes. An agent runs your React app at the PR's branch, checks changed screens with axe and the keyboard, and posts a review with a reproduction for each regression.

[Run this on Rush](https://cloud.getrush.ai/run/review-accessibility/react/), in a Project that has your repository.

## What the agent does

The agent starts from the pull request, not from a generic React checklist. In an app shaped like cypress-io/cypress-realworld-app, `package.json` pins `react` and `react-dom` 18.2.0, `react-router-dom` 5.3.4, `@mui/material` 5.15, `formik` 2.4.6 and `xstate` 4.38.3, with Vite 8, TypeScript 5.8.3, Cypress 16 and Vitest 3 in development; `engines` asks for Node 22 or 24 and `.nvmrc` says 22.20.0, with a `yarn.lock`. `yarn start:ci` runs Vite on port 3000 and the Express backend in `backend/app.ts` on 3001, as `.env` sets through `PORT` and `VITE_BACKEND_PORT`, and `cypress.config.ts` points `baseUrl` at 3000, seeds the database through a `db:seed` task that POSTs to `/testData/seed` on 3001, and runs component tests from `src/**/*.cy.tsx`. `.env` also sets `SEED_DEFAULT_USER_PASSWORD=s3cret`, and the `list:dev:users` script prints the seeded usernames from `data/database.json`, which is how the agent signs in.

It reads what the repository already does for accessibility, so the review adds rather than repeats. `eslint.config.mjs` loads `eslint-plugin-cypress` only; there is no `jsx-a11y` plugin and no axe dependency in `package.json`. `index.html` sets `lang="en"`. Across `src/components` the explicit ARIA is sparse: `NavBar.tsx` labels the drawer button `open drawer`, `NotificationListItem.tsx` labels its button `mark as read`, and `TransactionListAmountRangeFilter.tsx` ties its slider to `range-slider` with `aria-labelledby`. Forms such as `SignInForm.tsx` use MUI `TextField` with `label` props and `data-test` attributes, which gives inputs their names. `TransactionItem.tsx` renders MUI `Avatar` components with a `src` and no `alt` for the sender and receiver, and the whole list item carries an `onClick` on a `ListItem`, both of which the agent checks against the rendered DOM rather than the source alone.

For the pull request under review the agent clones the repository at the pull request's branch, runs `yarn install --frozen-lockfile` on the Node version from `.nvmrc`, starts the app with `yarn start:ci`, waits for both ports the way the `cypress-io/github-action` step in `.github/workflows/main.yml` does, and seeds the database. It reads the diff to list the components and routes the change touches, signs in with a seeded user and the default password, and opens each affected screen in its own browser in the states the change can produce: empty, loading, with validation errors, signed in and signed out. On each it runs axe-core from the browser, with no dependency added to the repository, and walks the screen with Tab, Shift+Tab, Enter, Space and Escape, recording focus order, visible focus, and whether dialogs and drawers trap and return focus. It then checks out the base branch and repeats the same screens, so every finding is a regression or a pre-existing problem, labelled as such.

The review is posted on the pull request as one review with a comment per finding, on the changed line in the component that renders the element. Each comment names the element and component, the route and state, the rule or keyboard step that failed, how to reproduce it, and a fix in MUI terms, such as an `alt` on the `Avatar`, an `aria-label` on an icon button, or a `role` and key handler on a clickable `ListItem`. Pre-existing problems the change did not cause go in the summary, not on lines. The agent writes `accessibility.md` with the full axe output per screen and the keyboard log, and pushes nothing.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "Review this React pull request for accessibility regressions only. Read package.json, .nvmrc, .env, cypress.config.ts, eslint.config.mjs, index.html and the changed components first. Install with yarn install --frozen-lockfile, start the app with yarn start:ci, wait for ports 3000 and 3001, and seed the database through the backend's testData endpoint. Sign in with a seeded user from data/database.json and the default password in .env. For each screen the diff touches, in every state the change can produce, run axe-core from your browser without adding a dependency, and walk the screen with the keyboard, recording focus order, visible focus and focus trapping in dialogs and drawers. Repeat the same screens on the base branch and mark each finding as a regression or pre-existing. Post one review with a comment per regression on the changed line, naming the element, component, route, state, failing rule or keyboard step, reproduction and an MUI-specific fix; put pre-existing problems in the summary. Write accessibility.md with the full axe output and keyboard log. Do not modify source or push commits.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app/pull/212"
    }
  ]
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

What comes back is a review on the pull request and `accessibility.md` as an artifact on the session. The review has one comment per regression, each anchored to the changed line in the component, with the route and state in which it appears, the axe rule id or the keyboard step that failed, the steps to see it, and a fix written against the MUI components the app uses. The summary lists pre-existing problems found on the same screens with the base branch so they can become their own issues, and names the screens and states that were checked and passed. No commit is pushed; the agent's branch checkout is read-only work in its sandbox.

A reviewer reads the regressions first and reproduces one with the steps given, then decides whether the pre-existing list deserves a follow-up. `accessibility.md` holds the full axe output per screen and the keyboard log, so a disputed comment can be traced to the rule and the DOM node that raised it. The session's transcript shows the install, the two servers starting, the seeding call and every page visit, so a reader can see which states were reached.

## Run it on every pull request that changes a component

An automation in the Project with a `github` trigger on `pull_request.opened` and `pull_request.synchronize` starts one run per push with this prompt; the trigger adds the pull request to the run after the prompt, so nothing in `input` needs filling in. Only pushes by an owner, member or collaborator fire it, never pull requests from forks. Each run works in its own sandbox and posts its own review.

To review only pull requests that touch `src/components`, a handler on your server receives GitHub's pull request webhook, reads the changed files, and sends `POST /o/{org}/sessions` with the pull request URL in `resources` for the ones that qualify, with a `request_id`, one UUID kept per head SHA, so a redelivered webhook returns the same run.

## Why run it on Rush?

An accessibility review needs the app running at the right commit, a browser, axe, a keyboard and the patience to visit every state twice, once on the branch and once on the base. The agent has all of that in its own sandbox and does the comparison for every pull request, so the review distinguishes what this change broke from what was already broken, which a static lint rule cannot do.

The review lands on the pull request where the discussion already is, with reproductions a developer can follow in a minute, and the full axe output stays in `accessibility.md` for anyone who wants the raw rule results. Several pull requests can be reviewed at once, each in its own sandbox.

## Frequently asked questions

### Does it add axe or an ESLint plugin to my repository?

No. The agent runs axe-core from its own browser against the running app and pushes nothing. `eslint.config.mjs` and `package.json` are unchanged. If you want `eslint-plugin-jsx-a11y` or `cypress-axe` in the repository, that is a separate run with a pull request.

### How does it tell a regression from an existing problem?

It runs the same screens and states on the base branch after the pull request's branch and compares. A finding present only on the branch is a regression and gets a line comment; one present on both goes in the review summary as pre-existing, so the author is not asked to fix what they did not break.

### Which states does it check?

The ones the diff can produce: empty lists, loading skeletons, validation errors on forms, and signed-in and signed-out views, following the routes the changed components render on. The transcript lists every page visit, and the review names the states that were checked so a reader knows what passed as well as what failed.

### What does it need to sign in?

The seeded users. `yarn start:ci` seeds `data/database.json`, `yarn list:dev:users` prints their usernames, and `.env` sets `SEED_DEFAULT_USER_PASSWORD=s3cret`. The agent uses those, so no real credentials are involved and nothing reaches an auth provider; the `dev:auth0`, `dev:okta` and `dev:cognito` scripts are not used.
