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

[a11y] Accessibility for nested text components #32004

Open
frags51 opened this issue Aug 11, 2021 · 26 comments
Open

[a11y] Accessibility for nested text components #32004

frags51 opened this issue Aug 11, 2021 · 26 comments
Assignees
Labels

Comments

@frags51
Copy link

frags51 commented Aug 11, 2021

Description

Same as (stale) #27147.
Consider this example:

<Text>
    <Text> Please click:  </Text>
    <Text accessible={true} accessibilityRole='link' onPress={openLink()}> Link </Text>
</Text>

A screen reader user is not able to click/open the link (i.e. the 2nd nested Text component). Ideally, the screen reader should focus on the link as well. The addition of any permutation of accessibility props on the parent/child Text component does not help.

React Native version:

React native 0.62.
React Native Environment Info:
System:
OS: Windows 10
CPU: (12) x64 AMD Ryzen 5 5600X 6-Core Processor
Memory: 19.72 GB / 31.92 GB
Binaries:
Node: 12.21.0 - C:\Users\supsing\AppData\Local\Temp\yarn--1628676998345-0.3639484914346074\node.CMD
Yarn: 1.22.10 - C:\Users\supsing\AppData\Local\Temp\yarn--1628676998345-0.3639484914346074\yarn.CMD
npm: 6.14.11 - C:\Users\supsing\AppData\Local\nvs\default\npm.CMD
Watchman: 20210102.202219.0 - E:\watchman-v2021.01.04.00-windows\bin\watchman.EXE

Steps To Reproduce

Write nested text components as shown in the description.

Expected Results

Screen reader (Voiceover/talkback) should be able to tell the user that there is a link and user should be able to click on that link.

In Android (native), talkback adds any links available in a single text view in the accessibility menu (can be opened using swipe up then swipe right). The same should be done by React Native as well. Perhaps it can be achieved with ClickableSpan in Android. There should be similar ways in iOS as well.

Snack, code example, screenshot, or link to a repository:

<Text>
    <Text> Please click:  </Text>
    <Text accessible={true} accessibilityRole='link' onPress={openLink()}> Link </Text>
</Text>
@frags51
Copy link
Author

frags51 commented Aug 12, 2021

There is no easy solution for this, even if try to create empty, pressable Views just where the pressable link is (using absolute positioning). This is because, onTextLayout/onLayout does not fire for the nested Text elements and hence it is not possible to know their position.

@jsamr
Copy link

jsamr commented Oct 4, 2021

@amarlette, that's a big issue for consumers of our library https://github.com/meliorence/react-native-render-html, do you think this issue could be addressed in the context of the GAAD Pledge?

@jsamr
Copy link

jsamr commented Oct 16, 2021

Possible duplicate of #31298

@tj-mc
Copy link

tj-mc commented Feb 1, 2022

@jsamr Do you have any related issue threads from react-native-render-html that might help us understanding how this issue is affecting users?

@jsamr
Copy link

jsamr commented Feb 1, 2022

@tj-mc At least one, yes! meliorence/react-native-render-html#285
The issue was also raised in our Discord server, such as here.

@tj-mc
Copy link

tj-mc commented Feb 2, 2022

Thanks. I'm doing my best to understand what technical solution would be required here, and if it's within my reach, as I also feel this is an important issue.

I agree @jsamr that this is a duplicate of #31298. @frags51, Would we consider closing this issue, and continuing this conversation in #31298?

@fabOnReact
Copy link
Contributor

In Android (native), talkback adds any links available in a single text view in the accessibility menu (can be opened using swipe up then swipe right). The same should be done by React Native as well.

as documented in my test case fabOnReact/react-native-notes#9 (comment) this was solved with commit b352e2d on Android

A screen reader user is not able to click/open the link (i.e. the 2nd nested Text component). Ideally, the screen reader should focus on the link as well.

currently working on fabOnReact/react-native-notes#9 (comment). thanks

facebook-github-bot pushed a commit that referenced this issue Mar 29, 2022
Summary:
This issue fixes [32004][23]. The Pull Request was previously published by [blavalla][10] with [31757][24].
>This is a follow-up on [D23553222 (https://github.com/facebook/react-native/commit/b352e2da8137452f66717cf1cecb2e72abd727d7)][18], which made links functional by using [Talkback's Links menu][1]. We don't often use this as the sole access point for links due to it being more difficult for users to navigate to and easy for users to miss if they don't listen to the full description, including the hint text that announces that links are available.
The Implementation of the functionality consists of:

Retrieving the accessibility links and triggering the TalkBack Focus over the Text
1. nested Text components with accessibilityRole link are saved as [ReactClickableSpan][17] instances in Android native [TextView][20] ([more info][19])
1. If the TextView contains any [ClickableSpans][15] (which are [nested Text][14] components with role link), set a view tag and reset the accessibility delegate.
3. Obtain each link description, start, end, and position relative to the parent Text (id) from the Span as an [AccessibilityLink][16]
4. Use the [AccessibilityLink][16]  to display TalkBack focus over the link with the `getVirtualViewAt` method (more [info][13])

Implementing ExploreByTouchHelper to detect touches over links and to display TalkBack rectangle around them.
1. ReactAccessibilityDelegate inherits from [ExploreByTouchHelper][12]
2. If the [ReactTextView][21] has an accessibility delegate, trigger ExploreByTouchHelper method [dispatchHoverEvent][22]
3.  Implements the methods `getVirtualViewAt` and `onPopulateBoundsForVirtualView`.
     The two methods implements the following functionalities  (more [info][13]):
    * detecting the TalkBack onPress/focus on nested Text with accessibilityRole="link"
    * displaying TalkBack rectangle around nested Text with accessibilityRole="link"

## Changelog

[Android] [Added] - Make links independently focusable by Talkback

Pull Request resolved: #33215

Test Plan:
[1]. User Interacts with links through TalkBack default accessibility menu ([link][1])
[2]. The nested link becomes the next focusable element after the parent element that contains it. ([link][2])
[3]. Testing accessibility examples in pr branch ([link][3])
[4]. Testing accessibility android examples in pr branch ([link][4])
[7]. TalkBack focus moves through links in the correct order from top to bottom (PR Branch with [link.id][25]) ([link to video test][7]) ([discussion][26])
[8]. TalkBack focus does not move through links in the correct order from top to bottom (PR Branch without [link.id][25]) ([link to video test][8]) ([discussion][26])

Test on main branch
[5]. Testing accessibility examples in main branch ([link][5])
[6]. Testing accessibility android examples in main branch ([link][6])

[1]: fabOnReact/react-native-notes#9 (comment)
[2]: fabOnReact/react-native-notes#9 (comment)
[3]: fabOnReact/react-native-notes#9 (comment)
[4]: fabOnReact/react-native-notes#9 (comment)
[5]: fabOnReact/react-native-notes#9 (comment)
[6]: fabOnReact/react-native-notes#9 (comment)
[7]: fabOnReact/react-native-notes#9 (comment)
[8]: fabOnReact/react-native-notes#9 (comment)

[10]: https://github.com/blavalla "blavalla github profile"
[12]: https://github.com/aosp-mirror/platform_frameworks_base/blob/1ac46f932ef88a8f96d652580d8105e361ffc842/core/java/com/android/internal/widget/ExploreByTouchHelper.java#L48 "com/android/internal/widget/ExploreByTouchHelper.java#L48"
[13]: fabOnReact/react-native-notes#9 (comment) "explanation of getVirtualViewAt and onPopulateBoundsForVirtualView"
[14]: https://github.com/aosp-mirror/platform_frameworks_base/blob/1ac46f932ef88a8f96d652580d8105e361ffc842/core/java/android/text/Spannable.java#L3 "core/java/android/text/Spannable.java#L3"
[15]: https://github.com/fabriziobertoglio1987/react-native/blob/561266fc180b96d6337d6c6c5c3323522d66cc44/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java#L70-L71 "react/views/text/ReactTextViewManager.java#L70-L71"
[16]: https://github.com/fabriziobertoglio1987/react-native/blob/561266fc180b96d6337d6c6c5c3323522d66cc44/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java#L680-L685 "react/uimanager/ReactAccessibilityDelegate.java#L680-L685"
[17]: https://github.com/facebook/react-native/blob/561266fc180b96d6337d6c6c5c3323522d66cc44/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java#L126-L129 "react/views/text/TextLayoutManager.java#L126-L129"
[18]: b352e2d
[19]: #30375 (comment) "explanation on how nested Text are converted to Android Spans"
[20]: https://github.com/aosp-mirror/platform_frameworks_base/blob/1ac46f932ef88a8f96d652580d8105e361ffc842/core/java/android/widget/TextView.java#L214-L220 "core/java/android/widget/TextView.java#L214-L220"
[21]: https://github.com/facebook/react-native/blob/485cf6118b0ab0b59e078b96701b69ae64c4dfb7/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java#L577 "dispatchHoverEvent in ReactTextView"
[22]: https://github.com/aosp-mirror/platform_frameworks_base/blob/1ac46f932ef88a8f96d652580d8105e361ffc842/core/java/com/android/internal/widget/ExploreByTouchHelper.java#L120-L138 "dispatchHoverEvent in ExploreByTouchHelper"
[23]: #32004
[24]: #31757
[25]: https://github.com/fabriziobertoglio1987/react-native/blob/485cf6118b0ab0b59e078b96701b69ae64c4dfb7/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java#L648 "setting link.id in the AccessibilityLink constructor"
[26]: https://github.com/facebook/react-native/pull/33215/files/485cf6118b0ab0b59e078b96701b69ae64c4dfb7#r820014411 "comment on role of link.id"

Reviewed By: blavalla

Differential Revision: D34687371

Pulled By: philIip

fbshipit-source-id: 8e63c70e9318ad8d27317bd68497705e595dea0f
@blavalla
Copy link
Contributor

@frags51 , has this been resolved by @fabriziobertoglio1987 's PR?

@frags51
Copy link
Author

frags51 commented May 30, 2022

@fabriziobertoglio1987 Is this working on iOS as well? @blavalla if not, then it needs to be solved for iOS too.

@github-actions github-actions bot added Needs: Attention Issues where the author has responded to feedback. and removed Needs: Author Feedback labels May 30, 2022
@tanyalsweeney
Copy link

Is there at least an iOS workaround? I'm experiencing this now

@pranjal-jately-unmind
Copy link

Thanks for addressing this on Android, @fabriziobertoglio1987. This is not working on iOS currently, and it would be great to have a fix for that as well, thanks.

@volpstar
Copy link

Why wouldn't you just wrap the text in a Touchable? Semantically, this is more accurate I think.

@fabOnReact
Copy link
Contributor

fabOnReact commented Dec 16, 2022

I tested the following scenarios:

  • the Nested Text has accessibilityRole link
  • It is possible to focus with the screenreader on the link
  • Voiceover announces the link
  • It is possible to click on the link (I tested with a console.log)

The functionality works on the main branch. I remain available. Thanks

Testing iOS nested text without accessibilityRole link

VID_20221219_153333.mp4

Related Links:

Saadnajmi pushed a commit to Saadnajmi/react-native-macos that referenced this issue Jan 15, 2023
Summary:
This issue fixes [32004][23]. The Pull Request was previously published by [blavalla][10] with [31757][24].
>This is a follow-up on [D23553222 (https://github.com/facebook/react-native/commit/b352e2da8137452f66717cf1cecb2e72abd727d7)][18], which made links functional by using [Talkback's Links menu][1]. We don't often use this as the sole access point for links due to it being more difficult for users to navigate to and easy for users to miss if they don't listen to the full description, including the hint text that announces that links are available.
The Implementation of the functionality consists of:

Retrieving the accessibility links and triggering the TalkBack Focus over the Text
1. nested Text components with accessibilityRole link are saved as [ReactClickableSpan][17] instances in Android native [TextView][20] ([more info][19])
1. If the TextView contains any [ClickableSpans][15] (which are [nested Text][14] components with role link), set a view tag and reset the accessibility delegate.
3. Obtain each link description, start, end, and position relative to the parent Text (id) from the Span as an [AccessibilityLink][16]
4. Use the [AccessibilityLink][16]  to display TalkBack focus over the link with the `getVirtualViewAt` method (more [info][13])

Implementing ExploreByTouchHelper to detect touches over links and to display TalkBack rectangle around them.
1. ReactAccessibilityDelegate inherits from [ExploreByTouchHelper][12]
2. If the [ReactTextView][21] has an accessibility delegate, trigger ExploreByTouchHelper method [dispatchHoverEvent][22]
3.  Implements the methods `getVirtualViewAt` and `onPopulateBoundsForVirtualView`.
     The two methods implements the following functionalities  (more [info][13]):
    * detecting the TalkBack onPress/focus on nested Text with accessibilityRole="link"
    * displaying TalkBack rectangle around nested Text with accessibilityRole="link"

## Changelog

[Android] [Added] - Make links independently focusable by Talkback

Pull Request resolved: facebook#33215

Test Plan:
[1]. User Interacts with links through TalkBack default accessibility menu ([link][1])
[2]. The nested link becomes the next focusable element after the parent element that contains it. ([link][2])
[3]. Testing accessibility examples in pr branch ([link][3])
[4]. Testing accessibility android examples in pr branch ([link][4])
[7]. TalkBack focus moves through links in the correct order from top to bottom (PR Branch with [link.id][25]) ([link to video test][7]) ([discussion][26])
[8]. TalkBack focus does not move through links in the correct order from top to bottom (PR Branch without [link.id][25]) ([link to video test][8]) ([discussion][26])

Test on main branch
[5]. Testing accessibility examples in main branch ([link][5])
[6]. Testing accessibility android examples in main branch ([link][6])

[1]: fabOnReact/react-native-notes#9 (comment)
[2]: fabOnReact/react-native-notes#9 (comment)
[3]: fabOnReact/react-native-notes#9 (comment)
[4]: fabOnReact/react-native-notes#9 (comment)
[5]: fabOnReact/react-native-notes#9 (comment)
[6]: fabOnReact/react-native-notes#9 (comment)
[7]: fabOnReact/react-native-notes#9 (comment)
[8]: fabOnReact/react-native-notes#9 (comment)

[10]: https://github.com/blavalla "blavalla github profile"
[12]: https://github.com/aosp-mirror/platform_frameworks_base/blob/1ac46f932ef88a8f96d652580d8105e361ffc842/core/java/com/android/internal/widget/ExploreByTouchHelper.java#L48 "com/android/internal/widget/ExploreByTouchHelper.java#L48"
[13]: fabOnReact/react-native-notes#9 (comment) "explanation of getVirtualViewAt and onPopulateBoundsForVirtualView"
[14]: https://github.com/aosp-mirror/platform_frameworks_base/blob/1ac46f932ef88a8f96d652580d8105e361ffc842/core/java/android/text/Spannable.java#L3 "core/java/android/text/Spannable.java#L3"
[15]: https://github.com/fabriziobertoglio1987/react-native/blob/561266fc180b96d6337d6c6c5c3323522d66cc44/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java#L70-L71 "react/views/text/ReactTextViewManager.java#L70-L71"
[16]: https://github.com/fabriziobertoglio1987/react-native/blob/561266fc180b96d6337d6c6c5c3323522d66cc44/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java#L680-L685 "react/uimanager/ReactAccessibilityDelegate.java#L680-L685"
[17]: https://github.com/facebook/react-native/blob/561266fc180b96d6337d6c6c5c3323522d66cc44/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java#L126-L129 "react/views/text/TextLayoutManager.java#L126-L129"
[18]: facebook@b352e2d
[19]: facebook#30375 (comment) "explanation on how nested Text are converted to Android Spans"
[20]: https://github.com/aosp-mirror/platform_frameworks_base/blob/1ac46f932ef88a8f96d652580d8105e361ffc842/core/java/android/widget/TextView.java#L214-L220 "core/java/android/widget/TextView.java#L214-L220"
[21]: https://github.com/facebook/react-native/blob/485cf6118b0ab0b59e078b96701b69ae64c4dfb7/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java#L577 "dispatchHoverEvent in ReactTextView"
[22]: https://github.com/aosp-mirror/platform_frameworks_base/blob/1ac46f932ef88a8f96d652580d8105e361ffc842/core/java/com/android/internal/widget/ExploreByTouchHelper.java#L120-L138 "dispatchHoverEvent in ExploreByTouchHelper"
[23]: facebook#32004
[24]: facebook#31757
[25]: https://github.com/fabriziobertoglio1987/react-native/blob/485cf6118b0ab0b59e078b96701b69ae64c4dfb7/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java#L648 "setting link.id in the AccessibilityLink constructor"
[26]: https://github.com/facebook/react-native/pull/33215/files/485cf6118b0ab0b59e078b96701b69ae64c4dfb7#r820014411 "comment on role of link.id"

Reviewed By: blavalla

Differential Revision: D34687371

Pulled By: philIip

fbshipit-source-id: 8e63c70e9318ad8d27317bd68497705e595dea0f
@Drew-Gerber
Copy link

We're considering using this approach for handling inline links on iOS: https://benjie.ca/blog/2021/09/21/accessible-inline-links-react-native.html

It's not perfect, but at least makes the links available to VoiceOver users. An alternative approach might be to use an accessibility action.

RikSchefferAmsterdam pushed a commit to Amsterdam/amsterdam-app-frontend that referenced this issue Jun 22, 2023
De bron van het probleem is een bug / missende feature in RN: [FAQ in `react-native-render-html`](https://meliorence.github.io/react-native-render-html/docs/faq#some-anchors-a-are-not-accessible-to-screen-readers).

Ik begrijp op zich wel dat het focussen op of voorlezen van een link in een alinea wat complexiteit geeft om te implementeren. Focus: tik je op de alinea of de link? Voorlezen: hoe geef je aan dat de ‘link’ rol alleen op die link van toepassing is?

Het idee van de oplossingen (of liever workarounds) die ik tegenkom, is dat de HTML gesplitst wordt in losse tekstcomponenten, zodat hun rol individueel voorgelezen kan worden en ze individueel te focusen zijn. Qua voorlezen is dat niet ideaal, omdat er pauzes midden in een zin komen. Ook qua weergave niet, omdat je normaal gesproken witregels krijgt tenzij je die weer wegwerkt. Als we het voorlezen mikken op niet-ziende mensen is dat laatste geen probleem. En in elk geval beter dan de huidige situatie waarin de link helemaal niet te vinden is.

Voor deze eerste, POC-achtige implementatie probeer ik HTML te splitsen in paragrafen met en zonder links. Alternatief zou misschien een custom renderer zijn. Ik weet nog niet of we eerst moeten mikken op de korte fragmentjes HTML die we in de afvalwijzer tegenkomen of ook al moeten rekening houden met inline links in bijv. de Werkzaamheden-proza. Ook vraag ik me af of het qua performance acceptabel is om dit steeds te splitsen, al hoeven we het alleen te doen als we zien dat een schermlezer (of toetsenbord?) actief is.

Ik heb alvast een hook `isScreenReaderEnabled` toegevoegd, kopie van `isReduceMotionEnabled`. Doet het nog niet, waarschijnlijk omdat ik nog niet helemaal door heb of dit nu op het juiste moment geëvalueerd wordt. Ook betwijfel ik of die `callbackAfterAppStateChange` wel meegenomen moet worden.

Referenties:
* facebook/react-native#32004
* https://www.callstack.com/blog/react-native-android-accessibility-tips (wordt ook gelinkt in die FAQ hierboven)
* https://benjie.ca/blog/2021/09/21/accessible-inline-links-react-native.html

Related work items: #81491
@carlaTatiana
Copy link

carlaTatiana commented Jun 22, 2023

I have the same problem!

<Text>My text {' '}
<Text accessibilityRole='link' onPress={ ()=> openFirstLink}>First link</Text>
<Text accessibilityRole='link' onPress={ ()=> openSecondLink}>Second link</Text>
</Text>

In my case, I have 2 nested links. On Android, the screen reader recognize the links, but on iOS it doesn't

@stevehanson
Copy link

Has anyone found a solution for this that works for more than one link in a paragraph? I am building a news app for a major publication. We must meet WCAG AA, which I believe all apps should aim for, and I am struggling to find a path forward. This issue has been literally keeping me up at night.

We don't have the time or resources at this stage in the project to upgrade to the new architecture, which seems risky anyways, given that Expo only just now released experimental support (we're on Expo), and a quick spike I did that uncovered some bugs we'd have to resolve.

I'm shocked that React Native has such a fundamental accessibility gap here and that there hasn't been an effort to port the fix to the old architecture.

@tj-mc
Copy link

tj-mc commented Oct 31, 2023

I agree this is an unfortunate gap. Is it solved in the new architecture?

Perhaps if you have rich text content, an inline webview might be a better solution. You can load local HTML and render without a loading time if it's static.

It seems like most news apps I use are native wrappers around web layouts.

@stevehanson
Copy link

Thanks for this suggestion, @tj-mc! I converted this portion of my app to use webviews instead, and this completely solved my accessibility issues. Screen reader, voice control, keyboard all work exactly as expected. It's a shame that we have to use webviews as an escape hatch, and I certainly wouldn't consider this issue as solved because of that option, but I am glad to have found a way to move forward.

@frags51
Copy link
Author

frags51 commented Nov 4, 2023

@stevehanson The changes shown in this PR could be a workaround.

Webview/HTML definitely helps but do consider the perf/memory implications of using one.

@msftedad
Copy link

msftedad commented Feb 8, 2024

Hi team, Any update on this issue?

@chriszs
Copy link

chriszs commented Apr 18, 2024

After testing with Accessibility Inspector on iOS Simulator and TalkBack on the Android emulator, my conclusion is that this does appear to be adequately solved on the new architecture on iOS and with TalkBack on the old renderer, but is not solved on the old renderer on iOS, as also noted in #35193.

In the old architecture on iOS, VoiceOver reads the content of the link, but it is not possible to independently select it with VoiceOver or open the link using VoiceOver alone. This could vary on actual devices, but that's what I'm seeing in the simulator.

I realize this amounts to a summary of what others have concluded above, but I was confused reading through the comments on this issue and wanted to confirm whether it had been addressed and the scope.

@msftedad
Copy link

@chriszs, The role of link must be announced to user as if it does not announce the role user who depends on Voiceover will be impacted , Also Voice over is announcing it as Static text with no other information like double tap to activate the link.

@chriszs
Copy link

chriszs commented Apr 23, 2024

@msftedad Good point. Do you think that works okay on the new architecture and Android (where Android offers to provide a list of links after the paragraph has been read)?

@msftedad
Copy link

Hi @chriszs, This was discussed and as per the Inputs, this will be read on Android as well, so user will be aware of the information in paragraph.

@msftedad
Copy link

@chriszs , Any update?

@yanachrnsh
Copy link

yanachrnsh commented Aug 28, 2024

I faced the same issue, if I put Text inside Text with accessibility role "link" and onPress, on the Android emulator it says that link can be opened using swipe up then swipe right, but on iOS it does not recognise as it is link

<Text>Any text 
    <Text accessibilityRole='link' onPress={ ()=>{}}>Link</Text>
</Text>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.