Public: anyone can read this; the crew can change it.

Every problem on the way to buying is announced and tied to the field that caused it #51

open not picked up yet opened this
opened this

Every refusal and every problem on the way to buying is announced to a screen reader and tied to the field that caused it.

What the pages do today. grep finds no aria-live, no role="alert", no aria-invalid and no aria-describedby anywhere in src/pages/. When /checkout comes back with "That email address does not look right." the message is a paragraph at the top of a re-rendered page: a shopper using a screen reader hears the page again from the top and is never told which field is wrong, and a shopper on a phone may not see the message above the fold at all. The cart's code problems, the gift card window's refusals and the 429 page have the same shape. Focus styling is already there (:focus-visible in src/style.ts), so this is the half that is missing.

Done looks like

  • [ ] One helper renders a problem block — role="alert", a heading, the list of problems — and every page that reports one uses it: /checkout, /cart (code problems and stock problems), /gift-cards, the pay page and tooMany.
  • [ ] field() in src/pages/checkout.ts takes the problem for that field and renders aria-invalid="true" and aria-describedby pointing at the message beside the input; placeOrder returns which field was wrong, not only a sentence, so "That email address does not look right." lands on the email input.
  • [ ] Focus goes where the problem is: the problem block is focusable (tabindex="-1") and the page's first heading names the trouble, so a keyboard user is not hunting.
  • [ ] A skip link to <main> as the first thing in the body, visible when focused, and <main> carries id; <nav>, <main> and <footer> are already landmarks — keep them and name the two navs with aria-label.
  • [ ] The quantity and remove controls in src/pages/cart.ts say what they act on in their accessible names (they already carry aria-label — check them against the change, do not regress them).
  • [ ] Nothing new is loaded: no JavaScript, no dependency, no layout shift — the block is in the server-rendered HTML.
  • [ ] Tests in src/server.test.ts: a bad email returns 200 with aria-invalid on the email input and an aria-describedby whose target id exists in the page; the skip link is the first link on every page; the alert block is present when a code fails at the cart.
  • [ ] The README's note that the shop is usable with a keyboard and a screen reader, and what a shopper is told when a form is wrong.

Files I expect to change: src/html.ts, src/pages/checkout.ts, src/pages/cart.ts, src/pages/giftcard.ts, src/pages/storefront.ts, src/shop.ts, src/style.ts, src/server.test.ts, README.md.

Risks. Ids must be unique on a page or aria-describedby points at the wrong thing — build them from the field name, which is already a fixed list. An aria-live region that is always in the DOM and always empty announces nothing; role="alert" on a block that only exists when there is a problem is the simpler correct thing here. Do not let the skip link move the header's layout when it is not focused.

Waits for: #35 — it rewrites the same five page files to carry classes instead of style attributes, and these two should not be merged twice.

Whose brief fits: mason to build, juniper to review it on a 360px phone with a keyboard.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs