Context
PR #157 added a new-tab cue to every target="_blank" link in apps/web: <span className="sr-only"> (opens in new tab)</span> where the link has visible text, or appended to the aria-label where it does not. That is 16 copies across 9 files (AppFooter, AppHeader, ProjectCard, ActivityCard, LoginPlaceholder, Account, PersonDetail, ProjectDetail, Volunteer).
Problem
The cue text, the rel="noopener noreferrer", and the target are re-typed at every site. A future link will forget one of them, and changing the wording (or adding a visual icon) means touching 16 places.
Proposal
An ExternalLink component in apps/web/src/components/ that takes href + children, sets target/rel, and appends the sr-only cue (or merges it into a supplied aria-label). Migrate the 16 sites; the existing accessible-name assertions in the test suite (regex matchers on /\(opens in new tab\)$/) cover the migration.
Deferred from plans/a11y-mechanical.md (PR #157).
Context
PR #157 added a new-tab cue to every
target="_blank"link inapps/web:<span className="sr-only"> (opens in new tab)</span>where the link has visible text, or appended to thearia-labelwhere it does not. That is 16 copies across 9 files (AppFooter,AppHeader,ProjectCard,ActivityCard,LoginPlaceholder,Account,PersonDetail,ProjectDetail,Volunteer).Problem
The cue text, the
rel="noopener noreferrer", and thetargetare re-typed at every site. A future link will forget one of them, and changing the wording (or adding a visual icon) means touching 16 places.Proposal
An
ExternalLinkcomponent inapps/web/src/components/that takeshref+ children, setstarget/rel, and appends the sr-only cue (or merges it into a suppliedaria-label). Migrate the 16 sites; the existing accessible-name assertions in the test suite (regex matchers on/\(opens in new tab\)$/) cover the migration.Deferred from
plans/a11y-mechanical.md(PR #157).