Skip to content

chore(lint): enforce jsx-a11y/label-has-associated-control - #234

Merged
BigSimmo merged 1 commit into
mainfrom
claude/a11y-lint-hardening
Jul 3, 2026
Merged

chore(lint): enforce jsx-a11y/label-has-associated-control#234
BigSimmo merged 1 commit into
mainfrom
claude/a11y-lint-hardening

Conversation

@BigSimmo

Copy link
Copy Markdown
Owner

What

Adds one accessibility lint rule — jsx-a11y/label-has-associated-control at error — and fixes the 4 pre-existing violations it surfaces. Config-only + a small, self-contained markup fix. No new dependencies.

Why

Follow-up to the 2026-07 DOM/a11y audit. eslint-config-next enables only a narrow jsx-a11y subset (alt-text, aria-props, role validity), so a <label> that isn't wired to a control passes a clean lint. This closes that gap and enforces it in CI.

The 4 fixes: the browse-library filter labels in ClinicalDashboard (Type/Site/Topic/Population) sat next to aria-labeled <select>s but weren't associated with them. Each is now linked via htmlFor/id (the aria-label stays as the descriptive name).

Deliberately scoped out

I measured the sibling rule control-has-associated-label (which flags an unlabeled <input>/<select> — the exact regression class from the audit) and did not enable it: it false-positives on this codebase's pervasive <label><span>text</span><input/></label> pattern (~13 correctly-labeled controls across auth-panel, forms, DocumentManagementActions, master-search-header, …), and depth tuning doesn't clear them. Enabling it — even at warn — would flood lint with noise on correct code. Precise unlabeled-control detection is better done with a runtime axe-core check (no false positives), which needs a dependency + test wiring and is tracked as a separate task.

Testing

  • eslint (full scope) — 0 errors (rule enforced; the 20 remaining warnings are pre-existing no-unused-vars)
  • tsc --noEmit — clean
  • prettier --check — clean

Clinical governance

Lint config + accessibility markup only. No ingestion, answer generation, search/ranking, source rendering, document access, privacy, or clinical output — governance preflight N/A.

🤖 Generated with Claude Code

eslint-config-next enables only a narrow jsx-a11y subset (alt-text,
aria-props, role validity), so <label>s not wired to a control pass a
clean lint. Enable label-has-associated-control at error to catch that
in CI, and fix the 4 pre-existing violations: the browse-library filter
labels in ClinicalDashboard sat next to aria-labeled <select>s without
being associated to them, so associate each via htmlFor/id.
Deliberately did NOT enable the sibling control-has-associated-label
(which flags an unlabeled input/select): it false-positives on this
codebase's pervasive <label><span>text</span><input/></label> pattern
(~13 correct controls) and depth tuning does not clear them. Precise
unlabeled-control detection belongs in a runtime axe-core check
(tracked separately).
lint (0 errors), typecheck, and prettier all clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@BigSimmo
BigSimmo merged commit 7af44c2 into mainJul 3, 2026
4 checks passed
@BigSimmo
BigSimmo deleted the claude/a11y-lint-hardening branch July 3, 2026 09:14
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@BigSimmo