Skip to content

fix(accessibility): Field announce error message for focused field #8329

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

majornista
Copy link
Collaborator

@majornista majornista commented May 30, 2025

Closes #8328

✅ Pull Request Checklist:

  • Included link to corresponding React Spectrum GitHub Issue #8328.
  • Added/updated unit tests and storybook for this change (for new code or code which already has tests).
  • Filled out test instructions.
  • Updated documentation (if it already exists for this component).
  • Looked at the Accessibility Practices for this feature - Aria Practices

📝 Test Instructions:

  1. Open the Spectrum 2 Preview > Form > Example storybook example.
  2. With a screen reader running, like VoiceOver on macOS or NVDA on Windows, navigate to move focus to the Email field, which is the third field in the form.
  3. Enter an invalid email value, like "admin", and press Enter to trigger validation by submitting the form.
  4. The screen reader should announce the error message that renders politely.
  5. Type some more to modify the value to another invalid value like, "admin@", then Tab or Shift+Tab to blur the input, which again trigger validation.
  6. After the screen reader announces the input that receives keyboard focus, it should announce "Please review Email field: Please enter a part following '@'. 'admin@' is incomplete," with the name and error message for the Email field that was blurred.

🧢 Your Project:

Accessibility/SUSI/Adobe

@majornista majornista added enhancement New feature or request accessibility labels May 30, 2025
@majornista majornista force-pushed the Issue-8328-field-error-announcement branch from db1dec4 to 71e40dc Compare May 30, 2025 21:05
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR fixes the error message announcement behavior for focused fields by updating the logic in the Field component to announce errors via the live announcer. Key changes include:

  • Adding a useEffect in Field.tsx to announce error messages when the field is focused.
  • Renaming the help text flag to hasErrorMessage for clarity.
  • Adding the @react-aria/live-announcer dependency in package.json.
  • Extending InlineAlert stories with a new DynamicWithAriaLivePolite variant.

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
packages/@react-spectrum/label/src/Field.tsx Updates error message announcement logic and variable naming
packages/@react-spectrum/label/package.json Adds dependency for live announcer
packages/@react-spectrum/inlinealert/stories/InlineAlert.stories.tsx Adds a new story variant showcasing aria-live behavior

@majornista majornista force-pushed the Issue-8328-field-error-announcement branch from 71e40dc to 3484b2f Compare May 30, 2025 21:20
@majornista majornista changed the title fix(#8328): Field announce error message for focused field fix(accessibility): Field announce error message for focused field May 30, 2025
@majornista majornista force-pushed the Issue-8328-field-error-announcement branch 3 times, most recently from fee6570 to af43a05 Compare May 30, 2025 22:04
@rspbot
Copy link

rspbot commented May 30, 2025

Build successful! 🎉

@majornista majornista requested a review from Copilot May 30, 2025 22:18
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR improves accessibility by ensuring that error messages are announced when the associated field is focused.

  • Introduces an effect in Field.tsx to announce error messages when the field is active.
  • Adds a dependency on @react-aria/live-announcer in package.json.
  • Updates stories and tests to better handle error message rendering.

Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

File Description
packages/@react-spectrum/label/src/Field.tsx Implements useEffect to announce error messages for focused fields.
packages/@react-spectrum/label/package.json Adds the live announcer dependency to support new accessibility logic.
packages/@react-spectrum/inlinealert/stories/InlineAlert.stories.tsx Introduces a new story variant with aria-live explicitly set.
packages/@react-spectrum/datepicker/test/DateField.test.js Modifies test to access the first instance of the error message element.
Comments suppressed due to low confidence (2)

packages/@react-spectrum/datepicker/test/DateField.test.js:239

  • Verify that multiple occurrences of the 'Date unavailable.' text are expected. If only one error message should appear, consider updating this to use a more specific selector to avoid masking potential duplication issues.
expect(tree.getAllByText('Date unavailable.')[0]).toBeInTheDocument();

packages/@react-spectrum/label/src/Field.tsx:71

  • Ensure that the ref passed to Field is always a RefObject. The current cast to RefObject assumes a stable object ref; if callback refs are used, this might lead to unexpected behavior when announcing error messages.
React.useEffect(() => {

@rspbot
Copy link

rspbot commented Jun 3, 2025

Build successful! 🎉

@majornista majornista requested a review from Copilot June 3, 2025 15:08
Copilot

This comment was marked as outdated.

@majornista majornista force-pushed the Issue-8328-field-error-announcement branch from fd00b91 to fbd7356 Compare June 3, 2025 15:09
@rspbot
Copy link

rspbot commented Jun 3, 2025

Build successful! 🎉

@rspbot
Copy link

rspbot commented Jun 3, 2025

Build successful! 🎉

@majornista majornista requested a review from Copilot June 4, 2025 13:45
Copilot

This comment was marked as outdated.

@majornista majornista force-pushed the Issue-8328-field-error-announcement branch from 3573692 to bd37367 Compare June 4, 2025 13:45
@rspbot
Copy link

rspbot commented Jun 4, 2025

Build successful! 🎉

@rspbot
Copy link

rspbot commented Jun 4, 2025

Build successful! 🎉

@majornista majornista force-pushed the Issue-8328-field-error-announcement branch from f862c17 to 72b183c Compare June 4, 2025 20:23
@majornista majornista requested a review from Copilot June 4, 2025 20:23
Copilot

This comment was marked as outdated.

@rspbot
Copy link

rspbot commented Jun 4, 2025

Build successful! 🎉

@rspbot
Copy link

rspbot commented Jun 6, 2025

Build successful! 🎉

Michael Jordan and others added 5 commits June 10, 2025 15:51
…blurred field

1. Improves announcement of validation error for just blurred field, by including accessible name in announcement for context.
2. Forgo announcement when the field receiving focus already has a validation error. It can be too much information.
3. Add alt text, "(valid)," to validationIcon, and include it in the `aria-describedby` for input for TextFieldBase.
4.  fix tests after adding alt text to validationIcon
@majornista majornista force-pushed the Issue-8328-field-error-announcement branch from 2fb28d7 to 4acca66 Compare June 10, 2025 15:51
@rspbot
Copy link

rspbot commented Jun 10, 2025

Build successful! 🎉

@rspbot
Copy link

rspbot commented Jun 10, 2025

Build successful! 🎉

@rspbot
Copy link

rspbot commented Jun 11, 2025

Build successful! 🎉

@rspbot
Copy link

rspbot commented Jun 11, 2025

Build successful! 🎉

@rspbot
Copy link

rspbot commented Jun 11, 2025

Build successful! 🎉

@rspbot
Copy link

rspbot commented Jun 11, 2025

Build successful! 🎉

@majornista majornista requested a review from Copilot June 12, 2025 17:46
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This pull request enhances accessibility by ensuring error messages for focused fields are appropriately announced and by refining ID and aria-describedby attributes across form components.

  • Introduces a valid state localized string and updates intl files.
  • Adjusts IDs and aria attributes for Input, DateField, DatePicker, DateRangePicker, ComboBox, and Autocomplete to improve accessibility announcements.
  • Enhances form validation logic with a timeout mechanism to announce error messages upon blur.

Reviewed Changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/@react-spectrum/textfield/intl/en-US.json Adds new locale string for valid state.
packages/@react-spectrum/label/src/Field.tsx Removes an unnecessary blank line.
packages/@react-spectrum/label/package.json Updates dependency versions and adds live announcer.
packages/@react-spectrum/inlinealert/stories/InlineAlert.stories.tsx Adds a new story with aria-live="polite".
packages/@react-spectrum/datepicker/test/DateField.test.js Adjusts test selector for improved reliability.
packages/@react-spectrum/datepicker/src/TimeField.tsx Adds ID and aria-describedby for valid icon accessibility.
packages/@react-spectrum/datepicker/src/Input.tsx Updates ID propagation and valid icon logic.
packages/@react-spectrum/datepicker/src/DateRangePicker.tsx Introduces useId for the input and assigns IDs conditionally on validation state.
packages/@react-spectrum/datepicker/src/DatePickerSegment.tsx Enhances merging of aria-describedby props.
packages/@react-spectrum/datepicker/src/DatePickerField.tsx Propagates the valid icon ID to segments for accessibility.
packages/@react-spectrum/datepicker/src/DatePicker.tsx Incorporates useId for improved ID management.
packages/@react-spectrum/datepicker/src/DateField.tsx Integrates improved ID handling and aria-describedby for a valid field.
packages/@react-spectrum/datepicker/intl/en-US.json Adds new valid state locale string entry.
packages/@react-spectrum/combobox/src/MobileComboBox.tsx Adds valid icon ID for improved validation accessibility.
packages/@react-spectrum/combobox/intl/en-US.json Updates locale strings to include a valid state.
packages/@react-spectrum/autocomplete/src/MobileSearchAutocomplete.tsx Adds valid icon ID and updates aria-labelledby join logic for accessibility.
packages/@react-spectrum/autocomplete/intl/en-US.json Updates locale strings to include a valid state.
packages/@react-aria/form/src/useFormValidation.ts Enhances error message announcement logic with a delay on blur.
packages/@react-aria/form/package.json Updates dependencies and adds dom-accessibility-api.
packages/@react-aria/form/intl/en-US.json Introduces new locale strings for invalid value and review field prompts.

@rspbot
Copy link

rspbot commented Jun 12, 2025

Build successful! 🎉

@rspbot
Copy link

rspbot commented Jun 16, 2025

Build successful! 🎉

@majornista majornista requested review from LFDanLu and dannify June 23, 2025 22:18
Copy link
Member

@LFDanLu LFDanLu left a comment

Choose a reason for hiding this comment

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

Verified the before and after behavior and confirmed that we are getting proper announcement when blurring from an errored field or if attempting to submit and invalid value. Will need to poke around/think some more on the change in useFormValidation though

@@ -262,7 +263,8 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut
props['aria-labelledby'],
props['aria-label'] && !props['aria-labelledby'] ? props.id : null,
valueId,
validationState === 'invalid' ? invalidId : null
validationState === 'invalid' ? invalidId : null,
validationState === 'valid' ? validId : null
Copy link
Member

Choose a reason for hiding this comment

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

oh huh, I probably should've done this in #8435 instead

Copy link
Collaborator Author

@majornista majornista Jun 24, 2025

Choose a reason for hiding this comment

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

I think it's a simpler approach for including the validation state in the labelling of the popup button used for the combobox on mobile, and it's the same for both invalid and valid states.

Copy link
Member

Choose a reason for hiding this comment

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

I'll go ahead and update my PR to do that instead, thanks! Will hopefully make the conflicts easier to resolve for this PR and/or we can defer to your PR instead

return (
<span {...fieldProps} data-testid={props['data-testid']} className={classNames(datepickerStyles, 'react-spectrum-Datepicker-segments', inputClassName)} ref={ref}>
{state.segments.map((segment, i) =>
(<DatePickerSegment
aria-describedby={i === 0 ? validIconId : undefined}
Copy link
Member

@LFDanLu LFDanLu Jun 23, 2025

Choose a reason for hiding this comment

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

perhaps doesn't have to be done in this PR, but I wonder if we could instead modify useDateSegment to handle this for us automatically since it already tries to only apply aria-describedby to the first segment. If would need to accept user provided aria attributes as well as the ones from useDateField but would allow this logic to be non-RSP specific

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

See comment below: #8329 (comment)

Comment on lines +57 to +60
if (ariaDescribedByProp) {
// Merge aria-describedby from segmentProps and otherProps
segmentProps['aria-describedby'] = segmentProps['aria-describedby'] ? `${segmentProps['aria-describedby']} ${ariaDescribedByProp}` : ariaDescribedByProp;
}
Copy link
Member

Choose a reason for hiding this comment

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

as mentioned previously, might be good to handle this in the hooks to make it non-RSP specific, but definitely can be followup

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

See comment below: #8329 (comment)

import {mergeProps, mergeRefs, useEvent, useLayoutEffect, useResizeObserver} from '@react-aria/utils';
import React, {ReactElement, useCallback, useRef} from 'react';
import textfieldStyles from '@adobe/spectrum-css-temp/components/textfield/vars.css';
import {useFocusRing} from '@react-aria/focus';
import {useLocalizedStringFormatter} from '@react-aria/i18n';

export const VALID_ICON_POSTFIX = '-valid-icon';
Copy link
Member

Choose a reason for hiding this comment

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

bit unfortunate that this needs to be imported in several places, but fine for now IMO. It would be nice if the hooks or something could handle this automatically for us, maybe via generating the label for us and applying it on a hidden span or something

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The valid icon is an React-Spectrum thing and, in the case of the DatePicker and DateRangePicker, is contained within the Input that wraps the DateFields, which contain the segments. I wrestled with the best way to handle this without altering APIs.

Copy link
Member

Choose a reason for hiding this comment

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

fair enough, will have to update this for S2 as well I'd imagine then but fine for now

Comment on lines +121 to +130
if (
(!input || !input.validationMessage) ||
(relatedTarget && isValidatableElement(relatedTarget) && (relatedTarget as ValidatableElement).validationMessage)
) {
// If the input has no validation message,
// or the relatedTarget has a validation message, don't announce the error message.
// This prevents announcing the error message when the user is navigating
// between inputs that may already have an error message.
return;
}
Copy link
Member

Choose a reason for hiding this comment

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

just to make sure I'm understanding the expected behavior here:
the polite error message will announce if you navigate to a field that isn't in an error state from one that was invalid but NOT if you are going between multiple invalid fields since we prefer the user to fix the error on the field they just landed on?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You are understanding correctly, when the field receiving focus and the invalid field being blurred both announce it becomes pretty confusing.

LFDanLu added a commit that referenced this pull request Jun 24, 2025
github-merge-queue bot pushed a commit that referenced this pull request Jun 24, 2025
…imilar support for MobileCombobox (#8435)

* preventing talkback from focusing valid icon and adding to mobile combobox

* update string, match some logic from #8329
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accessibility enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Announce Field error message for a focused field more reliably on Form submission
3 participants