hotwired / turbo

The speed of a single-page web application without having to write any JavaScript
https://turbo.hotwired.dev
MIT License
6.54k stars 415 forks source link

Accessibility issues with Turbo navigation #774

Open khiga8 opened 1 year ago

khiga8 commented 1 year ago

Hi Turbo team,

Our team migrated to using turbo drive and turbo frame in our Rails app around 3 months ago. We have received reports and discovered that turbo navigation poses accessibility issues for our screen reader users. Some of these issues, we can alleviate on our end through better guidance and additional accessibility feedback updates. Other issues, we do not see any workarounds for. We wanted to share our investigation and the feedback we’ve gotten with you.

Findings

Expected behavior

With a standard, hard page navigation, there are default mechanisms built in that allow a browser to communicate to a screen reader user that page navigation has taken place. For instance, when page navigation happens, the page title will be announced by the screen reader.

In addition, some screen readers have settings that can be toggled to specify what happens when loading a new web page like “Speak webpage summary” or “Automatically speak the webpage”. When these settings are toggled, a screen reader user can expect certain announcements to be made on page load.

Turbo frame

When a turbo frame navigation happens, there is no feedback given to screen reader users to indicate anything has happened.

Turbo drive

Workarounds and Caveats

Other issues

Because the focus can get completely lost on a turbo frame or turbo drive navigation, focus management is necessary. . We have found that some of this comes down to us using turbo frame when we shouldn’t be, but others of these, we’ll need to carefully manage focus, We are still figuring out how focus management should be handled in various scenarios but wanted to call out that this is a problem.

Videos

We've recorded some videos in a sandbox environment to demonstrate these findings below:

https://user-images.githubusercontent.com/16447748/197065530-6dee9d4a-df29-4a9d-9118-ae684c2f1a12.mp4

This video demonstrates the issues of turbo drive with VoiceOver/Safari.

https://user-images.githubusercontent.com/16447748/197065550-fb4f5572-183a-4f98-878d-dc31abd5a863.mp4

This video demonstrates the issues of turbo drive with NVDA/Chrome.

https://user-images.githubusercontent.com/16447748/197065564-5095cbd9-f5d8-44af-907b-6a33d61ab2f4.mp4

This video demonstrates the issues of turbo frame with NVDA/Chrome.

In summary

While websites built in Rails can come with some accessibility benefits baked in, and Turbo enables these systems to incorporate more app-like behaviors, the way that Turbo is handling some specific cases of navigation negate those accessibility benefits.

We wanted to pass along our findings so that these concerns can be addressed appropriately and more accessibility guidance can be provided to consumers of Turbo, especially since Turbo is reaching wider adoption in the Rails community.

manuelpuyol commented 1 year ago

Here's the sandbox code in case anyone wants to test it out https://github.com/hotwired/turbo/compare/main...manuelpuyol:a11y-showcase

brunoprietog commented 1 year ago

Maybe it's controversial what I'll say, but as a blind user, one of the things I like most about SPAs is that I don't have to hear notices that a page changed or hear the page summary every time it loads. I simply enter on a link and continue using the application normally as if nothing happened. I understand that this doesn't respect the screen reader settings when changing pages, but that's really what you're looking for, to have the feeling that the page doesn't reload.

However, the focus management issue can be a bit awkward. For example, the button to add a star to a GitHub repository. If you press it, the screen reader should indicate that now the button is not to add a star, but to remove it. But that doesn't happen, the screen reader simply mutes and you have to move with up and down arrows to identify that the same button now says something else. I know that's not a turbo frame, but it's an example that has similar behavior and produces the same effect. I'm used to it and it doesn't bother me, but I admit that it can be confusing.

Another thing that might be useful is that Firefox with NVDA always announces page changes. On GitHub, this translates to always hearing page announcement messages twice, unless navigating using turbo frames.

While this might be a problem, I think there are other much more important accessibility issues. In my opinion, I would not consider these types of errors to be serious accessibility issues.

Thinking of some proposal to handle focus, one could listen to a global or Turbo frame visit and focus on some important element, such as the main region or an important element of the Turbo frame. I think this is much more comfortable than receiving the focus at the top of the page, but this is more a matter of personal taste and it is very difficult to reach agreements.

Maybe there could be some meta tag that contains the id of an element that turbo should focus on the next visit. There could also be another meta tag containing the announcement for screen readers.

Another option could be to default to announcing the page title every time a change occurs on the page. I don't think it's such a big deal to have a message announced twice, considering that most users stop announcements that are no longer relevant with the control key.

For a temporary solution, the announcement message could be in a permanent div with aria-live. Instead of replacing the body element every time turbo makes a visit, one would have to listen for the turbo:before-visit event, and look for an element like div#app, changing the element that is replaced. This trick works since Turbo 7.2 and is also recommended when there are iframes from external services that do not survive the replacement of the body element. In this way, the delay of the announcement to screen readers could be removed.

In addition, it is currently possible to focus on a specific element with the autofocus attribute each time turbo performs a visit. While the specification says that autofocus works with button, input, select and textarea HTML elements, I have been able to verify that it works with any element that can be focused by the browser using Turbo.

I think it is unfair to say that an application that uses Turbo has serious accessibility problems. As a blind user I really appreciate using applications like Hey, Basecamp or GitHub itself, even when you started using Turbo and did not announce page changes, and while it may be a drawback, I think it is much more serious not to respect things like header levels, regions, modal dialogs, cmboboxes, menus, etc.

I don't think developers using Turbo are making applications inaccessible just because of that. In my opinion, the conclusion is more alarmist than it really is.

Probably this discussion goes beyond what is expected from accessible web browsing in a SPA. Gatsby, for example, forces the screen reader to read the entire contents of the newly loaded page in all cases. Next.js only reads the title of the new page, which it extracts from the page title or a level 1 header.

Summarizing, I think a good starting point would be for Turbo to announce by default the change of a page title when browsing, giving some option to set up a custom announcement and an element to focus during the next visit.

What do you think? It would be interesting to hear more experiences from screen reader users.

manuelpuyol commented 1 year ago

hi @brunoprietog, thanks so much for your input!

It's nice to know that you had positive experiences using Turbo, and we are going to try some of your suggestions, like announcing during before-visit.

Summarizing, I think a good starting point would be for Turbo to announce by default the change of a page title when browsing, giving some option to set up a custom announcement and an element to focus during the next visit.

I think this is spot on. Even though some issues may not be that bad, each application that uses Turbo has to implement some form of announcement/focus management, and if Turbo provided the tools for it, it'd make the navigation more standard for users.

brunoprietog commented 1 year ago

Hi @manuelpuyol,

I discovered something else yesterday.

If you apply morphdom when replacing instead of using replaceWith, I notice a big improvement in focus management.

import morphdom from "morphdom"

addEventListener("turbo:before-render", ({ detail }) => {
  detail.render = morphdom
})

For example, if I am in a navigation bar that has links and the link representing the current page is marked with aria-current="page", when I press enter on one of the links that redirects to a new page, the focus is not lost and the screen reader immediately says "current page".

So, I thought the same could result for turbo frames, and there is a big improvement. The problem is that sometimes it fails but I can't detect the error, and I don't know if there is some Turbo error when evolving an incorrect turbo frame or morphdom is not able to process the new turbo frame.

What do you think about this? Would you be willing to investigate something like this?

seanpdoyle commented 1 year ago

Thank you for this thorough report!

Summarizing, I think a good starting point would be for Turbo to announce by default the change of a page title when browsing

In the past, I've attempted to resolve this shortcoming by including a visually hidden element with aria-live="assertive" as part of the server-rendered response. That element's usually the first child of the <body>:

<html>
  <head>
    <title><%= content_for(:page_title) %></title>
    <!-- the rest of the head -->
  </head>
  <body>
    <span class="sr-only" aria-live="assertive"><%= content_for(:page_title) %></span>
    <!-- the rest of the body -->
  </body>
</html>

Is that a suitable polypill for built-in announcements? Would that be a good pattern to codify into Turbo or Turbo Rails?

brunoprietog commented 1 year ago

Yes, it would be appropriate in my opinion, hopefully in Turbo, and after turbo:before-render, so that no one can avoid it unintentionally by replacing the body with one that doesn't include the announcement.

We have to remove the element after a few milliseconds or seconds, so that the user does not read that announcement at the top or bottom of the page. This is something that almost nobody does.