Skip to content

fix(browser): read a native label for any labelable element, not just form fields - #1169

Merged
divshekhar merged 3 commits into
reticlehq:mainfrom
drakeo338:claude/1145-fix
Sep 28, 2026
Merged

divshekhar merged 3 commits into
reticlehq:mainfrom
drakeo338:claude/1145-fix

Conversation

@drakeo338

Copy link
Copy Markdown
Contributor

What & why

Closes #1145

A <button role="combobox"> (or <meter>/<output>/<progress>) with a native <label for> was reported as unnamed: getAccessibleName only read el.labels inside the input/textarea/select guard, so anything outside that set fell through to its own text content or returned ''. { role: "combobox", name: "…" } lookups on a correctly labelled control found nothing. This hoists the labels read out of the guard so it runs for any labelable element, before the input-specific fallbacks and before name-from-content.

How it was verified

Added a unit test with the button/label markup from the issue. Reverted the fix locally and ran the added test: it failed (2 failing) without the change, and passes (0 failing) with it — confirming the test exercises the fix, per the AI-assisted contribution guideline.

Gates run

  • pnpm lint && pnpm typecheck && pnpm test:unit (~2 min — always)
  • pnpm test:e2e (~8 min) — touched the tool surface, core, an observer, or telemetry
  • pnpm gate:install (~15 min) — touched reticle init, vite-plugin, next, or babel-plugin
  • pnpm test:e2e:desktop (~3 min) — touched adapters/realm/electron, adapters/realm/tauri, or desktop capture
  • None of the above tiers apply to this change

Checklist

  • Every commit is signed off (git commit -s) — CI's DCO check fails the PR without it. Already pushed? git rebase --signoff origin/main && git push --force-with-lease
  • Tests added/updated (RED → GREEN); the change is covered by a test that would fail without it
  • No any, no free strings (wire strings live in @reticlehq/core), no non-null !
  • No console.log or internal tracking codes left in the diff
  • Each changed file is under the 1000-line cap
  • Docs updated, and a user-facing change adds a new file under .changes/ (never edit CHANGELOG.md — that file is assembled at release time, and editing it is what makes PRs conflict; format in .changes/README.md)
  • Security-affecting? Auth/redaction/trust-boundary changes keep the localhost-only, no-app-data-leaves-the-machine, no-arbitrary-JS posture (usage telemetry stays anonymous + opt-out per docs/telemetry.md) and are covered by a test

… form fields

el.labels was only read inside the isInput/isTextArea/isSelect guard in
getAccessibleName, so a <button role="combobox"> (or <meter>/<output>/
<progress>) with a native <label for> never picked up that label. It fell
through to its own text content (NAME_FROM_CONTENT) or returned '' for
roles outside that set, so by: role + name lookups found nothing.

Hoist the labels read out of the guard so it runs for any element with a
non-empty .labels, before the input-specific value/placeholder fallbacks
and before name-from-content.

Closes reticlehq#1145

Signed-off-by: drakeo338 <paranoyouz@gmail.com>
@github-actions

Copy link
Copy Markdown

Thanks for your first pull request to Reticle! A maintainer aims to review within two days. Before then, pnpm verify locally catches most of what CI will, and CONTRIBUTING.md explains the rest. If CI does not start, a maintainer needs to approve it for first-time contributors; that is normal.

@github-actions github-actions Bot added area/browser Affects packages/browser area/docs Documentation and docs site labels Sep 28, 2026
@greptile-apps

greptile-apps Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 4/5

[Medium risk] Changes how accessible names are computed for UI elements.

The PR appears safe to merge, with non-blocking hardening and regression-coverage improvements recommended.

Findings

  1. P2 Unrestricted labels read can throw ▶
  2. P2 Other labelable elements remain untested ▶

Summary

The PR moves native-label lookup ahead of content and input-specific fallbacks so labels can name buttons and other labelable elements.

  • Adds two button regression tests and a release note.
  • The broader element coverage is not tested, and the unconditional property read merits hardening.

Reviews (1) · Last reviewed commit: "fix(browser): read a native label for an..."

Comment thread adapters/realm/browser/src/dom/a11y.ts Outdated
Comment on lines +236 to +238
const labels = (el as Partial<HTMLInputElement>).labels;
if (labels !== null && labels !== undefined && labels.length > 0) {
const text = [...labels]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Unrestricted labels read can throw. Snapshots call getAccessibleName for non-labelable elements too. If a custom element has a labels property with a positive length that is not iterable, it passes this check and [...labels] throws, aborting the snapshot. Restrict the read to native labelable elements or validate the collection before spreading it.

}
});

it('names a plain button from its label', () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Other labelable elements remain untested. Both new cases create buttons, but the change and release note also cover meter, output, and progress. Add cases for those elements so their native-label behavior is protected against regressions.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

getAccessibleName read el.labels unconditionally, for every element a
snapshot walks - not only input/textarea/select/button/meter/output/
progress. A custom element defining its own labels property for
unrelated reasons (a tag list, a chart's category labels) could throw
on that read or return something that isn't a NodeList, breaking
naming for elements this function has no business reading .labels
from.

Add isMeter/isOutput/isProgress alongside the existing isInput/
isTextArea/isSelect/isButton cross-realm checks and gate the read on
that full labelable set, and add tests for meter/output/progress
labelled via a native <label for>, matching the release note's claim.

Signed-off-by: drakeo338 <paranoyouz@gmail.com>
@divshekhar

Copy link
Copy Markdown
Contributor

Fix is in the right place: getAccessibleName is the only reader of .labels, so query, snapshot and actions all pick it up. The new tests fail on main too. One nit: the (el as Partial<HTMLInputElement>).labels cast near a11y.ts:260 is unnecessary, because isLabelable already narrows el, and dropping it takes the !== undefined check with it. Kicked off CI; I will merge once it is green.

isLabelable already narrows el to the union of labelable elements, so
the Partial<HTMLInputElement> cast and the accompanying undefined
check were redundant.

Signed-off-by: drakeo338 <paranoyouz@gmail.com>
@drakeo338

Copy link
Copy Markdown
Contributor Author

Dropped the cast, since isLabelable already narrows el and the labels type no longer includes undefined.

@divshekhar divshekhar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cast is gone, thanks. Good to go once CI is green.

@divshekhar
divshekhar added this pull request to the merge queue Sep 28, 2026
Merged via the queue into reticlehq:main with commit 2fc3456 Sep 28, 2026
29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/browser Affects packages/browser area/docs Documentation and docs site

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[agent] A button named by <label for> is reported as unnamed

2 participants