Skip to content

Name a role="img" element the way ARIA actually names one - #34

Draft
bdelanghe wants to merge 1 commit into
mainfrom
claude/text-alternatives-aria-label
Draft

Name a role="img" element the way ARIA actually names one#34
bdelanghe wants to merge 1 commit into
mainfrom
claude/text-alternatives-aria-label

Conversation

@bdelanghe

Copy link
Copy Markdown
Collaborator

Closes #33.

validateTextAlternatives demanded an alt prop of anything with role="img". But alt is a content attribute of <img>/<area>/<input type=image> and exists nowhere else — so the rule was unsatisfiable for its own primary case, a graphic composed of other elements. Every way past it was a regression: an invalid alt on a <div>, a role="presentation" that discards the name, or dropping the role.

The svg arm ten lines down was already the control case: it calls hasAccessibleLabel() and accepts aria-label. The role="img" arm now does the same.

What changes

An image needs a name; alt is one way to carry one.

  • A non-<img> node with role="img" is checked with hasAccessibleLabel() — the same function the svg, icon-control, canvas and iframe arms already use.
  • A real <img> keeps the stricter reading: alt="" is the documented way to say "decorative", so an empty alt on an otherwise-unnamed image still reports as the distinct LONE_TEXT_EMPTY_ALT_MEANINGFUL rather than collapsing into a missing-alt finding.
  • <img> also stops being flagged when named by aria-label / aria-labelledby / title. That's a real relaxation and the correct one: the accessible-name computation prefers aria-label over alt, and it matches axe's image-alt rule, which accepts all three in alt's place. Calling it out explicitly since it's the one behaviour change beyond the reported bug.

What doesn't

An unnamed <div role="img"> is still an error, and an <img> with no name at all is still an error. Both have tests, alongside the two positive cases and the aria-labelledby variant.

Verified

336 tests pass, up from 330 — six added, none changed, none removed.

End-to-end through validate() + linkedom on the consumer's exact markup:

<div class="conf-bar" role="img" aria-label="22 met, 1 unmet, 14 not assessed of 37">
  <span class="conf-bar__seg conf-bar__seg--met"   style="width:59.46%"></span>
  ...
</div>
this branch clean — no findings
main [error] LONE_TEXT_MISSING_ALT $.children[0].children[0]

Heads-up: CI is red on main, and not because of this

deno task ci fails at its first step on an untouched checkout of main:

error: Found 3 not formatted files in 111 files
  CLAUDE.md
  .claude/settings.json
  .github/workflows/front-desk-add.yml

All three are missing a trailing newline, and all three are written by the org harness generator — standard.yml has been red on main since at least 2026-08-08 (runs 14, 17, 19). deno fmt here takes no path arguments, so it covers Markdown, JSON and YAML too, which is why lone is hit and baobab (whose task is scoped to src tests cli) is not.

I have deliberately not included that fix here — it's a different change with a different cause. I'll open it as its own PR so this one can go green, and file the generator side upstream, since reformatting the files by hand only holds until the next harness re-roll rewrites them.

No version bump in deno.jsonc — release cadence is yours to call.


Generated by Claude Code

`validateTextAlternatives` demanded an `alt` PROP of anything with `role="img"`.
But `alt` is a content attribute of <img>/<area>/<input type=image> and exists
nowhere else — so the rule was unsatisfiable for its own primary case, a graphic
composed of other elements. The three ways a consumer could get past it were all
regressions: an invalid `alt` on a <div>, a `role="presentation"` that discards
the name, or dropping the role. The svg arm ten lines down was already the
control case: it calls hasAccessibleLabel() and accepts aria-label.

Reported from a real consumer (#33): a segmented progress bar on
robertdelanghe.dev/conformance, correctly named with aria-label, flagged
LONE_TEXT_MISSING_ALT with no legal way to satisfy it.

WHAT CHANGES. An image needs a NAME; `alt` is one way to carry one. A non-<img>
node with role="img" is now checked with hasAccessibleLabel() — the same function
the svg, icon-control, canvas and iframe arms already use. A real <img> keeps the
stricter reading: alt="" is the documented way to say "decorative", so an empty
alt on an otherwise-unnamed image still reports as the distinct
EMPTY_ALT_MEANINGFUL rather than collapsing into a missing-alt finding.

<img> also stops being flagged when it is named by aria-label/aria-labelledby/
title instead of alt. That is a real relaxation, and the correct one: the
accessible-name computation prefers aria-label over alt, and it matches axe's
image-alt rule, which accepts all three in alt's place.

WHAT DOESN'T. An unnamed <div role="img"> is still an error, and an <img> with
no name at all is still an error. Both are covered by new tests, alongside the
two positive cases and the aria-labelledby variant.

Verified: 336 tests pass, up from 330, none changed. End-to-end through
validate() + linkedom on the consumer's exact markup — clean on this branch,
LONE_TEXT_MISSING_ALT on main.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011HAtCZXs1B5mUVWf6iTmdp
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

validateTextAlternatives rejects role="img" + aria-label, the only correct way to name a non-<img> graphic

2 participants