MotirBuilding in public
MOTIR · moooon
onMotir
You’re viewing a public project. Anyone can view it — no account needed. Sign in to submit, upvote, or comment on requests.View-only — you can’t edit work items
MOTIR-801

Epic-privacy toggle has no accessible name — Switch primitive drops aria-labelledby

Done
Description

Found while building the 6.14.9 epic-privacy E2E (MOTIR-439).

The epic-privacy toggle on the issue-detail page has NO accessible name — a WCAG 2.1 §4.1.2 (Name, Role, Value) defect on a form control.

Root cause (rung-2, shipped code):

  • app/(authed)/issues/[key]/_components/EpicPrivacyControl.tsx names its switch via aria-labelledby={labelId} (the id of the visible "Make this epic private" label span).
  • But the shared components/ui/Switch.tsx primitive only accepts an aria-label prop — it destructures a FIXED prop set and does NOT forward aria-labelledby (or any other aria-*) to the underlying role="switch" button.
  • Net: the aria-labelledby is silently dropped, so the rendered switch is role=switch with an EMPTY accessible name. Confirmed in the Playwright a11y snapshot: the node is bare - switch (no name), with the label only present as adjacent text.

Impact: a screen-reader user hears an unnamed toggle; getByRole("switch", { name }) cannot target it (the E2E falls back to the page-unique getByRole("switch")).

Fix options:

  1. Add aria-labelledby?: string to SwitchProps and forward it to the button (mirrors the existing aria-label pass-through) — then EpicPrivacyControl works as written; OR
  2. In EpicPrivacyControl, pass aria-label={t("epicPrivacyLabel")} to the Switch (the prop the primitive already supports) instead of aria-labelledby.

Option 1 is the more general fix (other call sites may want label association). Add a vitest/RTL assertion that the toggle has an accessible name, and the 6.14.9 E2E can then re-tighten to getByRole("switch", { name: "Make this epic private" }).

Acceptance criteria

  • The epic-privacy toggle exposes the accessible name "Make this epic private" (via the chosen mechanism); getByRole("switch", { name }) resolves it.
  • A unit/RTL test asserts the toggle has a non-empty accessible name.
  • No visual change; AA + shape/colour token rules unchanged.

Context refs

  • components/ui/Switch.tsx (the primitive — only aria-label is forwarded).
  • app/(authed)/issues/[key]/_components/EpicPrivacyControl.tsx (passes aria-labelledby).
  • tests/e2e/epic-privacy-flow.spec.ts (6.14.9 — currently selects the switch by role alone, with a comment pointing here).