Skip to content

fix(FormLabel): hide visual necessity indicator from screen readers to prevent double announcement#3366

Open
starboyvarun wants to merge 1 commit intorazorpay:masterfrom
starboyvarun:fix/form-label-double-screen-reader-announcement
Open

fix(FormLabel): hide visual necessity indicator from screen readers to prevent double announcement#3366
starboyvarun wants to merge 1 commit intorazorpay:masterfrom
starboyvarun:fix/form-label-double-screen-reader-announcement

Conversation

@starboyvarun
Copy link
Copy Markdown

Summary

FormLabel renders two things when necessityIndicator is required or optional:

  1. A VisuallyHidden node — invisible to sighted users, read aloud by screen readers (e.g. "required" or "optional")
  2. A visible indicator* for required, (optional) for optional — which is also read by screen readers

The result is a double announcement. For example, NVDA/VoiceOver reads "Email required asterisk" instead of "Email required". The (optional) case announces "Email optional open paren optional close paren".

The TODO comment at FormLabel.tsx:155 acknowledged this:

{/* TODO: Hide from screen readers to prevent double announcement */}
{necessityLabel}

Fix

Wrap the visible necessityLabel in makeAccessible({ hidden: true }). This emits:

  • Web: aria-hidden="true" on the wrapper element
  • React Native: accessibilityElementsHidden={true} + importantForAccessibility="no-hide-descendants"

makeAccessible is already used by other Form components (SelectorGroupField, SelectorInput) — this follows the same pattern.

// Before
{/* TODO: Hide from screen readers to prevent double announcement */}
{necessityLabel}

// After
{necessityLabel ? (
  <BaseBox {...makeAccessible({ hidden: true })}>{necessityLabel}</BaseBox>
) : null}

No visual change. The * and (optional) text continue to render as before — they are now just skipped by assistive technology since the VisuallyHidden node already announces the information semantically.

Testing

  • Required field: screen reader announces "[label] required" (not "required asterisk")
  • Optional field: screen reader announces "[label] optional" (not "optional open paren optional close paren")
  • necessityIndicator="none" (default): no change — necessityLabel is null so the wrapper is not rendered

The label rendered both a VisuallyHidden node ("required"/"optional" text
for screen readers) and a visible indicator (* or (optional)) without
hiding the visible one from AT. Screen readers announced both, e.g.
"required asterisk" instead of just "required".

Wrapping the visible necessityLabel in makeAccessible({ hidden: true })
emits aria-hidden on web and accessibilityElementsHidden /
importantForAccessibility on React Native, resolving the double
announcement without any visual change.
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 1, 2026

🦋 Changeset detected

Latest commit: 9bc10c5

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@razorpay/blade Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

✅ PR title follows Conventional Commits specification.

@codesandbox-ci
Copy link
Copy Markdown

codesandbox-ci Bot commented May 1, 2026

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 9bc10c5:

Sandbox Source
razorpay/blade: basic Configuration

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.

1 participant