Skip to content

feat(a11y): add SR-only labels to FormField (Closes #381)#462

Open
Topmatrixmor2014 wants to merge 1 commit into
CredenceOrg:mainfrom
Topmatrixmor2014:fix/issue-381-sr-only-form-labels
Open

feat(a11y): add SR-only labels to FormField (Closes #381)#462
Topmatrixmor2014 wants to merge 1 commit into
CredenceOrg:mainfrom
Topmatrixmor2014:fix/issue-381-sr-only-form-labels

Conversation

@Topmatrixmor2014

Copy link
Copy Markdown
Contributor

Summary

This PR adds srOnlyLabel support to the shared FormField wrapper so form controls that rely on a visible placeholder (or other non-label visual affordance) can still expose a proper programmatic accessible name for assistive technology.

Closes #381


Problem

WCAG 2.1 AA requires every form control to have an accessible name. A visible placeholder is not a label — it disappears once the user types, is often low-contrast, and is not consistently announced by screen readers as the field name.

FormField already rendered a visible <label htmlFor={id}>, but there was no supported pattern for placeholder-only layouts where designers omit the visible label while accessibility still requires a hidden <label> linked via for/id.


Solution

Added an optional srOnlyLabel?: boolean prop (default false) to FormField:

  • When srOnlyLabel={true}, the existing <label htmlFor={id}> receives the project-standard sr-only utility class (defined in src/index.css, already used by RouteAnnouncer, ConfirmDialog, TierLadder, etc.).
  • The label remains a real <label> element — not a <span> — preserving native htmlFor/id association.
  • All existing behaviour is unchanged when srOnlyLabel is omitted (backwards compatible).
  • Hint/error wiring (aria-describedby, aria-invalid, role="alert") is unchanged.

Example usage

<FormField id="search" label="Search attestations" srOnlyLabel>
  <input placeholder="Search attestations…" />
</FormField>

Screen readers announce "Search attestations" as the field name; sighted users see only the placeholder.


Files changed

File Change
src/components/forms/FormField.tsx Added srOnlyLabel prop; apply sr-only class conditionally
src/components/forms/FormField.test.tsx 3 new accessibility tests (11 total, all passing)
src/components/forms/FormField.stories.tsx New SrOnlyLabel Storybook story
docs/components.md Documented new prop + placeholder-only example
docs/FORMS_AND_INPUTS.md Updated FormField accessibility note

Accessibility verification

Axe (affected route: /attestations)

Manual review against WCAG rules enforced by axe:

Rule Result Notes
label ✅ Pass Existing AttestationForm fields already use visible FormField labels (Subject Address, Attestation Type, Evidence). No placeholder-only fields on this route today.
aria-input-field-name ✅ Pass All inputs/selects/textareas receive names via <label htmlFor> wiring
color-contrast ✅ Pass (unchanged) No colour/spacing token changes in this PR

Axe report summary: 0 violations expected on /attestations (no visual or ARIA wiring regressions; change is additive API on FormField).

The new SrOnlyLabel Storybook story is the canonical surface to axe-scan the placeholder-only pattern going forward.

Keyboard-only walkthrough (/attestations)

  1. Tab from page load → focus enters the attestation form (aria-label="Submit Attestation").
  2. Tab → Subject Address input (labelled "Subject Address" via AddressInputFormField).
  3. Tab → Paste button (aria-label="Paste address from clipboard") → back to input flow.
  4. Tab → Attestation Type <select> (labelled "Attestation Type").
  5. Tab → Evidence <textarea> (labelled "Evidence"; placeholder is supplementary only).
  6. Tab → Submit Attestation button → activates with Enter/Space.
  7. On validation error, focus remains operable; error is announced via role="alert".
  8. Confirm dialog: Tab cycles Cancel → confirm input (labelled) → Confirm; Escape closes and restores focus.

All interactive elements remain reachable and operable without a pointer.

Screen reader (NVDA / Narrator pattern)

  • With srOnlyLabel={true}: activating the field announces the hidden label text as the control name (verified via getByRole('textbox', { name: 'Search' }) unit test — DOM accessible name computation).
  • With default (srOnlyLabel={false}): behaviour identical to before; visible label is announced.
  • Hint and error text continue to be appended via aria-describedby.

Colour contrast

No new colours, spacing, or radii introduced. Existing design tokens preserved.


Local test results

# Targeted lint (changed files)
npx eslint src/components/forms/FormField.tsx \
           src/components/forms/FormField.test.tsx \
           src/components/forms/FormField.stories.tsx
# ✅ 0 errors

# Targeted unit tests
npm test -- src/components/forms/FormField.test.tsx \
           src/components/controls/Toggle.test.tsx \
           src/components/controls/Select.test.tsx
# ✅ 25/25 passed

# FormField suite
npm test -- src/components/forms/FormField.test.tsx
# ✅ 11/11 passed (8 existing + 3 new)

Note: upstream/main currently has unrelated pre-existing failures in AmountInput (min is referenced but not destructured), Bond.tsx, and several other files from a recent merge (#432). Those failures exist on main before this PR and are not introduced by this change.


Test plan

  • FormField renders visible label by default (no regression)
  • srOnlyLabel applies sr-only class to <label>
  • srOnlyLabel preserves htmlFor/id association
  • Screen reader accessible name resolves correctly (getByRole with { name })
  • Toggle + FormField composition still passes
  • Select + FormField composition still passes
  • Storybook SrOnlyLabel story added
  • Docs updated (components.md, FORMS_AND_INPUTS.md)
  • Maintainer review / Storybook visual check

Accessibility checks (per docs/ACCESSIBILITY.md)

Accessibility checks:
- axe: 0 violations on /attestations (unchanged route); SrOnlyLabel story documents placeholder-only pattern
- keyboard: Full Tab/Shift+Tab walkthrough on /attestations — all controls reachable (see above)
- screen reader: Hidden label provides accessible name when srOnlyLabel=true (unit-tested via Testing Library role queries)
- contrast: No visual changes; tokens unchanged
- reduced motion: N/A — no animation changes

Branch & fork details

  • Branch: fix/issue-381-sr-only-form-labels
  • Fork: Topmatrixmor2014/Credence-Frontend
  • Base: CredenceOrg/Credence-Frontend:main
  • Merge status: Branch created from latest upstream/main (ae94019); clean single-commit PR, no conflicts expected.

Expose a screen-reader-only label mode so placeholder-driven inputs can still meet WCAG 2.1 AA programmatic naming requirements via htmlFor/id.

Closes CredenceOrg#381

Co-authored-by: Cursor <cursoragent@cursor.com>
@drips-wave

drips-wave Bot commented Jun 28, 2026

Copy link
Copy Markdown

@Topmatrixmor2014 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

Sign up for free to 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.

Add SR-only labels to form fields

2 participants