From f6536bfff7a64d6e5e473851a252d260f808145c Mon Sep 17 00:00:00 2001 From: Replit Agent Date: Fri, 24 Apr 2026 14:43:56 +0000 Subject: [PATCH] docs(a11y): clickable-elements accessibility audit [Phase 2e] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audit-only PR — adds docs/accessibility/clickable-elements-audit.md catalogues every remaining clickable non-semantic UI pattern in frontend/src so the rest of the Phase 2 paydown can be sequenced from cheapest/safest to riskiest, with no surprises. No runtime code, no tests, no routes, no API calls, no business logic are changed by this PR. The repository has no Markdown linter configured (verified — no markdownlint / remark config under repo root or under frontend/), so no additional CI step is needed. Method ------ ripgrep across frontend/src for every pattern that Phases 2b/2c/2d each removed: *
/
  • / /
    * inline style={{cursor: 'pointer'}} * CSS module .x { cursor: pointer; } paired with non-semantic wrappers * AntD patterns * AntD patterns * role="button" without tabIndex / onKeyDown * tabIndex without a meaningful role * nested interactive controls inside clickable cards/rows Each hit was then manually verified by reading the surrounding JSX, so well-styled real
    ({ + onClick: () => navigate(`/warehouses/items?warehouse=${record.id}`), + })} + rowClassName={() => 'clickable-row'} + /> + ``` + The matching CSS rule lives in `frontend/src/theme/global.css` (lines 482 *and* 507 — the second copy uses `!important`, see [Finding 8](#8-themeglobalcss--duplicated--conflicting-clickable-row-rules)). +- **User impact:** AntD's `` has `role="row"` and is **not focusable by default**, so the entire warehouse list is keyboard-unreachable. Screen-reader users hear cells in turn but get no hint that the row navigates anywhere. AntD's row hover background `var(--brand-muted) !important` does not have a focus-visible parity rule. +- **Recommended fix:** Two viable directions, in order of preference: + 1. **Replace `onRow.onClick` with a real link in the first cell** — render the warehouse name in the leftmost column as an `` (or React Router ``). Browser-native focus, keyboard activation, screen-reader naming, middle-click "open in new tab" — all free. This is the AntD-recommended pattern for navigable rows. + 2. **Keep the row click, but make it accessible** — extend the `onRow` config with `tabIndex: 0`, `role: 'link'` (or keep `'row'` and add explicit keyboard handling), and `onKeyDown` for Enter/Space; add a `:focus-visible` outline to `.ant-table-tbody > tr.clickable-row`. This is more code but preserves the "click anywhere on the row" affordance. +- **Risk:** **Medium** (AntD wrapper, table semantics, route-sensitive markup, also touches a global CSS rule shared with any future `clickable-row` user). +- **Suggested phase:** Phase 2g. + +--- + +### 3. NotificationBell — AntD `List.Item onClick` with inline `cursor: pointer` + +- **File:** `frontend/src/components/Layout/NotificationBell.tsx` (lines 119–124) +- **Pattern:** + ```tsx + handleMarkRead(item.id)} + > + ``` + Wrapped in an AntD `Popover`. No `role`, no `tabIndex`, no `onKeyDown`. The notification icon trigger button itself is fine — only the per-item rows inside the popover are affected. +- **User impact:** Keyboard users opening the notifications popover can tab to "Mark all read" / "Clear all" / `Empty` placeholder, but **cannot activate individual notifications** to mark them read. Screen-reader users hear the notification text as static content with no indication that it is interactive. +- **Recommended fix:** Either replace `` with `` and migrate the inline `cursor: pointer` into a CSS module, **or** restructure the row so the only interactive element is a real `` without any focus-visible counterpart, which directly enables [Finding 2](#2-warehouseslist--antd-tableonrow-with-clickable-row-class). They also make the codebase look like there are two different clickable-row contracts, which there are not. +- **Recommended fix:** Pick one. When [Finding 2](#2-warehouseslist--antd-tableonrow-with-clickable-row-class) is fixed, also (a) collapse to the single AntD-scoped rule, (b) drop the `!important`, and (c) add a paired `:focus-visible` declaration so any future opt-in row gets the keyboard affordance for free. +- **Risk:** **Low** (CSS-only; small blast radius — one consumer today). +- **Suggested phase:** Phase 2g — bundle with [Finding 2](#2-warehouseslist--antd-tableonrow-with-clickable-row-class). + +--- + +## False positives (verified, **no fix needed**) + +These matched the search heuristics but proved to be real interactive elements on inspection. Listing them here so a future audit does not re-flag them. + +| File | Why it's fine | +| --- | --- | +| `pages/Dashboard/components/QuickActionsStrip.tsx` | Each action is a real `