Skip to content
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

Accessibility - screenreader announces "blank" while reading the options #5758

Merged
merged 17 commits into from
Nov 6, 2023

Conversation

Ke1sy
Copy link
Contributor

@Ke1sy Ke1sy commented Sep 26, 2023

This PR resolves this issue: #5121

  1. Added 'aria-activedescendant' for input and functionality to calculate it;
  2. Added role 'option' and 'aria-selected' for option;
  3. Added role 'listbox' for menu;
  4. Added tests for 'aria-activedescendant';
  5. Changes in aria-live region:
  • the instructions how to use select will be announced only one time when user focuses the input for the first time.
  • instructions for menu or selected value will be announced only once after focusing them.
  • removed aria-live for focused option because currently with correct aria-attributes it will be announced by screenreader natively as well as the status of this option (active or disabled).
  • separated ariaContext into ariaFocused, ariaResults, ariaGuidance to avoid announcing redundant information and higlight only current change.

Video result using NVDA:
https://alina-andreeva.tinytake.com/msc/ODczMTIyNF8yMjEzMjU5MA

@changeset-bot
Copy link

changeset-bot bot commented Sep 26, 2023

🦋 Changeset detected

Latest commit: b12107f

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

This PR includes changesets to release 1 package
Name Type
react-select Minor

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

@codesandbox-ci
Copy link

codesandbox-ci bot commented Sep 26, 2023

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 b12107f:

Sandbox Source
react-codesandboxer-example Configuration

@Ke1sy Ke1sy changed the title Accessibility improvements Accessibility - screenreader announces "blank" while reading the options Sep 26, 2023
@Ke1sy Ke1sy marked this pull request as ready for review September 29, 2023 14:50
@mellis481
Copy link

mellis481 commented Sep 29, 2023

@lukebennett88 @JedWatson @dcousens @nderkim @Rall3n @Methuselah96 This PR contains the addition of vital ARIA attributes that significantly improve the UX for screen reader users. Please merge and release ASAP. Thanks! 🙂

@lukebennett88
Copy link
Collaborator

Thanks so much @Ke1sy!
I've done an initial pass of the PR, and everything looks good to me so far. However, to make the review process a little smoother, would you mind adding a few comments on the PR to explain some of the changes and additions?

}, press Down to open the menu, ${
isMulti ? ' press left to focus selected values' : ''
}`;
return isInitialFocus
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This change is added to avoid hearing the instructions each time user's focus is moved back to the input. The instructions will be announced to user only on initial focusing of input.

@@ -98,25 +100,23 @@ export interface AriaLiveMessages<

export const defaultAriaLiveMessages = {
guidance: (props: AriaGuidanceProps) => {
const { isSearchable, isMulti, isDisabled, tabSelectsValue, context } =
const { isSearchable, isMulti, tabSelectsValue, context, isInitialFocus } =
Copy link
Contributor Author

Choose a reason for hiding this comment

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

isDisabled is removed because the screen reader will announce the default disabled message for the option after adding 'aria-activedescendant': "Blue unavailable 2 of 10"

const ScreenReaderText = (
<Fragment>
<span id="aria-selection">{ariaSelected}</span>
<span id="aria-context">{ariaContext}</span>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I separeted ariaContext into 3 blocks to allow only sufficient information be announced depending on the change instead of announcing large block (ariaFocused, ariaResults, ariaGuidance) all the time which causes negative user experience

@@ -605,7 +652,6 @@ export default class Select<
commonProps: any; // TODO
initialTouchX = 0;
initialTouchY = 0;
instancePrefix = '';
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I moved instancePrefix to state to be able to use it inside of getDerivedStateFromProps

@@ -441,6 +449,31 @@ function buildFocusableOptionsFromCategorizedOptions<
);
}

function buildFocusableOptionsWithIds<Option, Group extends GroupBase<Option>>(
Copy link
Contributor Author

@Ke1sy Ke1sy Oct 2, 2023

Choose a reason for hiding this comment

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

This function calculates focusable options with id as id is needed in 'aria-activedescendant'.

this.state.focusedOption = focusableOptions[optionIndex];
this.state.focusedOptionId = getFocusedOptionId(
Copy link
Contributor Author

@Ke1sy Ke1sy Oct 2, 2023

Choose a reason for hiding this comment

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

We need update focusedOptionId each time when focusedOption is updated. This works if menuIsOpen on init

this.setState({
focusedOption,
focusedOptionId:
focusedOptionIndex > -1 ? this.getFocusedOptionId(focusedOption) : null,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updating of focusedOptionId (aria-activedescendant) during hover

newMenuOptionsState = {
selectValue,
focusedOption,
focusedOptionId,
focusableOptionsWithIds,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

recalculation of focusableOptionsWithIds and focusedOptionId after some dependencies has changed (line 720)

@@ -921,6 +991,7 @@ export default class Select<
this.setState({
focusedOption: options[nextFocus],
focusedValue: null,
focusedOptionId: this.getFocusedOptionId(options[nextFocus]),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

after keyboard navigation

| readonly OptionBooleanValue[];
}

export const OPTIONS_GROUPED: readonly GroupedOption[] = [
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added this to test the 'aria-activedescendant' for grouped options as ther hve different format of id that includes the group index

@@ -844,6 +913,7 @@ export default class Select<
inputIsHiddenAfterUpdate: false,
focusedValue: null,
focusedOption: focusableOptions[openAtIndex],
focusedOptionId: this.getFocusedOptionId(focusableOptions[openAtIndex]),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

after search update

@Ke1sy
Copy link
Contributor Author

Ke1sy commented Oct 2, 2023

@lukebennett88 I added additional comments for changes, please feel free to ask if something else is not clear. Thanks!

@csandman
Copy link

csandman commented Oct 3, 2023

This PR is looking great! It would be awesome to see this package use more standard aria- attributes for it's accessibility implementation.

@Ke1sy
Copy link
Contributor Author

Ke1sy commented Oct 23, 2023

@csandman thank you for suggestions, I will take a look what I can do

@mellis481
Copy link

@lukebennett88 @Ke1sy has updated this PR to fallback to an aria-live-based solution for Apple devices. Other devices will have the significantly improved screen reader UX that @Ke1sy implemented that uses correct ARIA/roles. So please review at your earliest convenience.

The group enhancements that @csandman proposed are not included in this PR. They can be done in a follow-up PR.

@SairamAlagapan
Copy link

SairamAlagapan commented Oct 30, 2023

@mellis481 Can someone please approve and merge this?

@lukebennett88
Copy link
Collaborator

Just dropping in to say I've seen the PR has been updated to address the previous feedback and I will test and review when I get some free time.
Thanks again for all the hard work that went into this.

tsconfig.json Outdated Show resolved Hide resolved
Copy link
Collaborator

@lukebennett88 lukebennett88 left a comment

Choose a reason for hiding this comment

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

Amazing job @Ke1sy, I've tested this out in VoiceOver and NVDA and it all looks good to me.
I only have one minor nitpick question regarding the types.

@Ke1sy
Copy link
Contributor Author

Ke1sy commented Nov 6, 2023

@lukebennett88 th

Amazing job @Ke1sy, I've tested this out in VoiceOver and NVDA and it all looks good to me. I only have one minor nitpick question regarding the types.

Thanks!
@lukebennett88 you are right regarding the types, I rechecked and removed that line from tsconfig, hope we can merge this PR soon.

@lukebennett88 lukebennett88 merged commit 884f1c4 into JedWatson:master Nov 6, 2023
6 checks passed
@github-actions github-actions bot mentioned this pull request Nov 6, 2023
@@ -1624,9 +1721,12 @@ export default class Select<
'aria-labelledby': this.props['aria-labelledby'],
'aria-required': required,
role: 'combobox',
'aria-activedescendant': this.isAppleDevice
Copy link

Choose a reason for hiding this comment

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

When server-side rendering this hydration throws an error
Warning: Extra attributes from the server: aria-activedescendant
Setting either both to empty sting '' or undefined solves
'aria-activedescendant': this.isAppleDevice ? '' : this.state.focusedOptionId || ''
or
'aria-activedescendant': this.isAppleDevice ? undefined : this.state.focusedOptionId || undefined

Copy link

@khanakia khanakia Nov 13, 2023

Choose a reason for hiding this comment

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

any update on the issue i am also facing the error after update to 5.8.0?

Choose a reason for hiding this comment

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

Same. Setting instanceID helped fixed className hydration errors but aria-activedescendant still throws.

@giuliocaccin
Copy link

I'm so happy this awesome merge request got merged, I wanted to say thanks, because it made it unnecessary to use react-select-event to us, and brought a lot of clarity in all our tests, thus greatly improving developer experience. Impressive. Thanks a lot

@IlirEdis
Copy link

IlirEdis commented Feb 5, 2024

Hi there,
I'm getting Warning: Extra attributes from the server: aria-activedescendant in Nextjs v14.1.0 and seract-select v5.8.0

Screenshot 2024-02-05 at 13 46 29

Any help how i could fix this?

@jacobsfletch
Copy link

Hi there, I'm getting Warning: Extra attributes from the server: aria-activedescendant in Nextjs v14.1.0 and react-select v5.8.0

Same. Others too. Check out our conversation here: #5758 (comment)

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.

10 participants