What the pull request actually looks like
Everyone in this industry says they deliver "actionable" findings. Here is the whole thing, end to end, so you can judge the standard before spending anything.
This is a worked example, not a client's code. The defect is the exact pattern we found on four of the 27 Nordic webshops we audited; the markup is reconstructed so nobody is named. Everything else, including how it is verified and what we refuse to touch, is how the real thing arrives.
1. The finding, as you receive it
Element: div.store-select
Text: "Shipping to: Denmark"
Page: /checkout
The element has a click handler and cursor:pointer, so it looks and behaves
like a button for anyone using a mouse. It is not focusable and does not
respond to Enter or Space, so it cannot be reached or operated by keyboard.
Confirmed by hand: tabbed through the page from the top; focus never lands
on it. Pressing Enter and Space with it under the pointer does nothing.
axe-core does not report this. It reads the accessibility tree, where the
element is an ordinary div; the failure only shows when computed style,
event handlers and focusability are compared together.2. The change
Small, one concern, no reformatting, no drive-by refactoring of surrounding code.
return (
- <div
- className="store-select"
- onClick={() => setOpen(!open)}>
+ <button
+ type="button"
+ className="store-select"
+ aria-expanded={open}
+ aria-haspopup="listbox"
+ onClick={() => setOpen(!open)}>
Shipping to: {country}
- </div>
+ </button>
);A real <button> rather than a div with role and
tabindex bolted on. It is fewer lines, it gets focus, Enter and Space
for free from the browser, and it cannot regress the way a hand-rolled key handler
does. aria-expanded is there because the control opens a list and a
screen-reader user needs to know whether it is open.
When the markup genuinely cannot change
Sometimes a design system or a third-party component makes the element non-negotiable. Then it is the longer version, and we say so in the PR rather than pretending it was the first choice:
<div role="button" tabindex="0"
aria-expanded={open}
onClick={toggle}
onKeyDown={e => {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggle(); }
}}>
3. How we know it works
Every fix is re-run through the same browser check, and the result goes in the PR description. A fix that is not verified is a guess with a commit message.
Before: keyboard-inoperable controls: 1 (div.store-select)
After: keyboard-inoperable controls: 0
Re-checked in a real browser on the built branch:
- focus reaches the control by keyboard, in reading order
- Enter and Space both open the list
- Escape closes it and returns focus to the control
- the visible focus ring meets 3:1 against its background
- the page renders identically at 360px, 768px and 1280pxThat last line matters more than it looks. The common way an accessibility fix gets reverted is that it quietly changed the layout, so we check the page still looks right before we ask you to merge.
4. What we deliberately did not do
- No reformatting. The diff is the fix. If your formatter and ours disagree, ours loses.
- No unrelated fixes smuggled in. If we spot something else, it goes in the report, not this branch.
- No dependency added. Not for this, not for anything, unless we ask you first.
- No overlay widget, ever. They do not fix the markup, and disabled users broadly report that they make things worse.
- No claim that you are now compliant. One barrier is gone. That is a real improvement and it is not a legal opinion about your service.
5. When we cannot see your code
The diff above assumes we have your repository. Often we do not, and a rendered page does not tell us which template produced it. In that case the deliverable is the same change specified against the live element rather than a patch against files we have never seen:
Selector: div.store-select (checkout, above the payment step)
Replace: <div class="store-select" onclick="...">
With: <button type="button" class="store-select"
aria-expanded="false" aria-haspopup="listbox" onclick="...">
Also change the matching closing tag from </div> to </button>, and keep
aria-expanded in sync with the open state.
Apply this wherever that markup is produced. If your CSS targets div.store-select
by element rather than class, it will need the same treatment.We re-run the check against your live page once you have deployed it, and tell you whether it worked. A fix nobody verified is still a guess.
6. What it costs
The example above is one of the three fixes included in the 490 EUR audit, which also covers up to 25 pages checked in a real browser with every finding verified by hand. If we cannot find three issues worth fixing on your site, you get all of it back.