a11y: add main landmarks, drop bogus listbox roles, size footer tap targets - #478
a11y: add main landmarks, drop bogus listbox roles, size footer tap targets#478alukach wants to merge 1 commit into
Conversation
Three failures from a Lighthouse accessibility audit of the landing page. landmark-one-main: there was no <main> anywhere in src/, so this failed on every route, not just the landing page. Added to the (app) layout via asChild (no extra DOM node) and to the marketing page. On the marketing page main starts after <Navigation /> because Landing.module.css pins the nav background with `.landingInner > nav`, so the nav has to stay a direct child of that box — the sections below the fold stay outside the landmark as a result. aria-input-field-name: the product list declared role="listbox" with role="option" children, but the j/k shortcuts are a document-level keydown handler with no focus management and no aria-activedescendant. The roles promised a widget that was never implemented, the listbox had no accessible name, and role="option" may not contain the link each item renders. Dropped both roles — the <nav aria-label="Product list"> wrapper already carries the naming — and moved the highlight to aria-current. target-size: the four footer links are 0.75rem, giving a ~15px tall hit box against the 24px WCAG 2.2 minimum. inline-block plus vertical padding is what actually grows the box; padding alone on an inline element paints without changing hit testing. Refs #471
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Claude finished @alukach's task in 2m 0s —— View job ❌ Changes requested — see findings below. Verified the three fixes do what the PR description claims: the One real issue survived that check:
Everything else — the |
Part of #471.
Three accessibility failures from the Lighthouse audit. Independent of the perf PRs in the epic.
1.
landmark-one-main— no<main>anywheregrepfound zero<main>orrole="main"insrc/, so this failed on every route, not just the landing page.(app)layout — added via RadixasChild, so<main>replaces the existingBoxelement rather than adding a DOM node. Covers every app route.mainstarts after<Navigation />rather than wrapping the page, becauseLanding.module.csspins the nav background with.landingInner > nav. Hoisting the nav out would silently drop that background.Consequence worth flagging: the case-study and tout sections below the fold sit outside the landmark. Fixing that properly means restructuring the page's three sibling wrapper boxes, which is a real layout risk for a cosmetic gain — deliberately not bundled here.
2.
aria-input-field-name— a listbox that isn't oneProductsListdeclaredrole="listbox"withrole="option"children. ReadinguseProductListKeyboardShortcuts, the j/k navigation is a document-levelkeydownhandler — notabindex, no focus management, noaria-activedescendant. So the markup promised a widget that was never implemented, in three separate ways:role="option"may not contain interactive descendants, and each<li>wraps aProductListItemcontaining a link — screen readers may hide it.Fix is a deletion: drop both roles. The existing
<nav aria-label="Product list">already names the region, andaria-currentcarries the keyboard highlight without claiming widget semantics. Visual behavior is unchanged —isSelectedstill drives styling.3.
target-size— 15px footer linksThe four footer links render at
0.75rem, giving a ~15px tall hit box against the 24px WCAG 2.2 minimum.display: inline-blockis the load-bearing part: vertical padding on an inline element paints without growing the hit box. Widths (29–58px) already passed, so only height needed fixing.Verification
npx jest src/components/features/products src/components/layout "src/app/(app)/products"— 21 passed, 1 failed. The failure isDropdownSection.integration.test.tsx, which I confirmed fails identically on cleanmain(pre-existing, untouched by this PR).npm run type-check— 14 errors, identical to the baseline onmain.🤖 Generated with Claude Code