a11y: name the eight icon-only links, and make the a11y scan enforce it for <a> - #5825
Merged
Conversation
…it for <a> (#5674) `a11yConventions.test.js` required an accessible name from every icon-only <button>, <input>, <select> and <textarea>, but never asked it of an <a>. Eight links in the tree rendered nothing but a Lucide icon, so a screen reader announced "link" with no destination: the media lightbox and media card download links, the brain link-row and mood-board "open in new tab" links, the file browser and uploads download links, and the two LoRA source links. Seven leaned on `title`, which is mouse-hover-only and is not reliably read as the accessible name; two were worse — MediaLightbox had neither `title` nor `aria-label`, and MoodBoardReferenceStrip marked its only child `aria-hidden="true"`, computing a literally empty name. Each link now carries an `aria-label` alongside whatever `title` it already had, and <a> joins CONTROL_NAME_RULES so a new one fails the suite. Its allowlist is empty and documented to stay that way. The recognizer needed a naming path no form control has, because a link is named by its own content. It credits an anchor whose body renders text, without which the rule would report every ordinary text link in the tree; a descendant image's `alt`, without which three thumbnail links would have been "fixed" with an aria-label that OVERRODE the alt; and a same-file component that renders text, without which a card link (KanbanBoard's `<a><TicketCard /></a>`) would have been "fixed" the same destructive way. Only local declarations that render text are credited, so the imported icons every real offender wraps stay reported. All of it reads the body through one `aria-hidden`-masked preparation, so an anchor whose only child is hidden still reports as unnamed. `hasAccessibleControlName` now takes the tag-index node rather than a `(tag, index)` pair, since the body boundary is the lexer's answer to give.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
title(mouse-hover-only, not reliably read as the accessible name);MediaLightboxhad neithertitlenoraria-label, andMoodBoardReferenceStripmarked its only childaria-hidden="true", computing a literally empty name. Each now carries anaria-labelalongside whatevertitleit already had.<a>joinsCONTROL_NAME_RULESinclient/src/a11yConventions.test.js, reusing the existing per-tag scan and getting the stale-allowlist burn-down check for free.PREEXISTING_ANCHOR_NAME_ALLOWLISTis empty and documented to stay that way.hrefjoins the semantic-anchor attributes so an offender line names a link a reader can actually find.alt, without which three thumbnail links (<a><img alt={caption} /></a>) would have been "fixed" with anaria-labelthat overrode the alt;<a><TicketCard /></a>) would have been "fixed" the same destructive way. Only local declarations that render text are credited — the imported icons every real offender wraps (<Download />,<ExternalLink />) stay reported, and so does a file-local icon wrapper.aria-hidden-masked preparation, so an anchor whose only content is hidden still reports as unnamed.hasAccessibleControlNamenow takes the tag-index node rather than a(tag, index)pair, since the body boundary is the lexer's answer to give.The
alt-restricted-to-<img>/<area>and same-file-component branches came out of the local review round.Test plan
cd client && npx vitest run src/a11yConventions.test.js— 46 passed. The newreads a name for <a> from its own content, and from nothing elsefixture pins both directions so the rule cannot go vacuous: text and{label}bodies named; a bare icon child, atitle-only anchor, anaria-hidden-only child, an empty/absentalt, analton a<div>or a component, and a local icon-only component all unnamed;aria-label,<img alt>, and a local text-rendering component all named.cd client && npm test— 10736 tests, all green except one pre-existingMediaCollections.test.jsxnavigation flake that passes in isolation and touches nothing in this diff.cd client && npm run lint— clean.Closes #5674