# Review Accessibility in Next.js Pull Requests

Yes. An agent runs your Next.js storefront in a browser, runs axe and the keyboard on each changed page, and comments each new violation on the pull request diff.

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

## What the agent does

The agent checks out the pull request branch and reads the diff to find which routes it touches. In a Next.js app like vercel/commerce the App Router under `app/` has a short list: the home page, `app/product/[handle]/page.tsx`, `app/search/page.tsx` and `app/search/[collection]/page.tsx`, and the content pages at `app/[page]/page.tsx`. A change to `components/cart/modal.tsx` or `components/layout/navbar/index.tsx` touches every route, so the agent treats shared components as global.

Then it starts the app. `package.json` pins `next` to 15.6.0-canary.60 with React 19, `@headlessui/react` 2.2 for dialogs and `sonner` for toasts, has a `pnpm-lock.yaml`, and its only `test` script is `prettier:check`. The storefront reads `SHOPIFY_STORE_DOMAIN` and `SHOPIFY_STOREFRONT_ACCESS_TOKEN` in `lib/shopify/index.ts`, so the sandbox needs those two values from `.env.example` filled in, as Project secrets, or the pages render without products. The agent runs `pnpm install` and `pnpm dev`, opens each affected route in the sandbox browser, and runs axe on it twice: once on the base branch and once on the head, so only violations the change introduced become findings.

The keyboard pass follows the paths a shopper takes. It tabs through the navbar, where `components/layout/navbar/search.tsx` renders an input with a placeholder and no label, into the mobile menu and the cart, both Headless UI `Dialog` panels in `mobile-menu.tsx` and `cart/modal.tsx` with `aria-label` on their open and close buttons; it moves through the option buttons in `components/product/variant-selector.tsx`, which carry `aria-disabled` and a `title` for out-of-stock values; and it submits `add-to-cart.tsx`, whose `aria-live` status paragraph should announce the result. It checks focus goes into each dialog on open and back to the trigger on close, and that `components/product/gallery.tsx` buttons and images keep their `aria-label` and `alt` text.

Each finding becomes one review comment on the changed line: the axe rule id and the element it matched, or the keyboard step that failed, and the smallest fix. Pre-existing problems the change did not introduce are listed once in the review summary, not on the diff. The agent edits no source file and opens no second pull request; the review and an `accessibility.md` with the full axe output are what it returns.

## Start it with one API call

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

```json
{
  "agent": "claude",
  "input": "Review the open pull request on this Next.js storefront for accessibility. Read the diff to find the routes and shared components it touches. Fill SHOPIFY_STORE_DOMAIN and SHOPIFY_STOREFRONT_ACCESS_TOKEN from the Project's secrets, run pnpm install and pnpm dev, and open each affected route in the browser on the base branch and on the head branch. Run axe on each, and tab through the navbar search, the mobile menu and cart dialogs, the variant selector and the add-to-cart button, checking focus order, focus return on dialog close and the aria-live status text. Comment on the changed lines with each violation the change introduces, naming the axe rule or the keyboard step and the fix; list pre-existing violations once in the review summary and do not comment them on the diff. Write accessibility.md with the full axe output per route. Do not edit source files and do not open another pull request.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app/pull/42"
    }
  ]
}
```

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` on the session's artifacts. Each review comment sits on a changed line and names the element, the axe rule, for example `label` on an input with only a placeholder or `color-contrast` on a new badge, or the keyboard step, for example focus staying behind the cart panel after it closes, with the fix in a sentence. The summary lists violations that were already on the base branch so the team can file them separately.

`accessibility.md` holds the per-route axe results for base and head side by side, the keyboard paths the agent walked, and the routes it could not reach, for example a collection page that needs a product the test store does not have. A reviewer checks that each comment points at a line in the diff, that the rule named matches the element, and that a fix suggested for a Headless UI dialog respects how that component manages focus.

## Run it on every pull request opened or updated

Add an automation to the Project with this prompt and a `github` trigger on `pull_request.opened` and `pull_request.synchronize`, with `branch_patterns` set to `main` so only pull requests into the default branch start a run. Each push starts one run on that pull request's branch, and the review lands on the pull request as it would from a person. Only work by an owner, member or collaborator fires the trigger; pull requests from forks never do.

Without an automation, a handler for your GitHub webhook can send this request itself, replacing the `pull/42` resource with the pull request's URL and using the head commit as `Idempotency-Key`, so a retried delivery returns the run already started. The key's webhook then reports `run.completed` with the `session_url` for the review.

## Why run it on Rush?

Accessibility review is the review that gets skipped, because doing it properly means running the app, opening a browser and tabbing through every changed page, and nobody does that for a one-line change to a shared component. The agent does exactly that for each pull request in its own sandbox with its own browser, and the comments arrive with the rest of the review.

The findings come with proof: the axe output per route, the base-versus-head comparison that separates new problems from old ones, and the keyboard steps it took. That keeps the review honest in both directions: a pull request is not blamed for a label that was missing before it, and a real regression in focus handling is caught before it ships.

## Frequently asked questions

### The storefront needs a Shopify store. How does the sandbox get one?

Through Project secrets: save `SHOPIFY_STORE_DOMAIN` and `SHOPIFY_STOREFRONT_ACCESS_TOKEN` for a development store in the Project and start the session there, and the agent reads them as environment variables. Without them `lib/shopify/index.ts` has nothing to fetch and the agent reports that it could only check static pages.

### Does it report problems that existed before the pull request?

Not on the diff. It runs axe on the base branch and the head branch and comments only on violations that appear on head and not on base. Pre-existing violations go in the review summary as a list, which is where the search input's missing label would appear today.

### Which checks go beyond axe?

The keyboard pass: tab order through the navbar and product page, focus moving into the mobile menu and cart dialogs and back to their buttons on close, the variant selector's disabled options, and whether the `aria-live` region in the add-to-cart and cart quantity buttons announces its status. axe cannot see those; the agent records each step it took.

### Will it change the code or push to the branch?

No. The prompt forbids editing source files and opening another pull request. Each comment proposes the fix in words, and the author decides how to apply it.

### Can it review a pull request that adds a whole new page?

Yes. There is no base version to compare, so every violation on the new route is new and gets a comment on the nearest changed line, usually the component that renders the element. The summary says the route had no base to diff against.
