Gatsby: a11y issues: page nav doesn't trigger assistive tech

Created on 28 May 2018  Âˇ  36Comments  Âˇ  Source: gatsbyjs/gatsby

Description

Right now, using assistive tech to navigate between pages works, but doesn't announce that the user has reached the next page. This is confusing, so we should find a way to mimic standard navigation behavior when using gatsby-link for navigation.

Thanks to @nickcolley for pointing this out! Here were a few suggestions he came up with:

  • Focus the page after routing
  • Use ARIA live regions
  • Use a Service Worker to simulate server routing, but with less lag

    • This seems like the least desirable options, since it would require a full page re-render for all navigation; if someone wants this behavior, they could omit gatsby-link to get it.

Nick has done some research and mentioned he'll follow up on this issue with his findings.

This _may_ be an upstream fix for React Router, but let's make sure it's at least working with a11y in mind in Gatsby.

Steps to reproduce

  1. Visit https://gatsbyjs.org
  2. Start VoiceOver (command + F5)
  3. Use the tab key to focus on a link in the gatsbyjs.org navigation
  4. "Click" the link using control + option + space

It will announce that it is following the link, but will not announce the next page.

Expected result

After navigation, the new page should be announced. For a working example, visit https://smashingmagazine.com, activate VoiceOver, and navigate to the Articles page.

Actual result

After navigation, nothing happens.

Environment

  • Gatsby version (npm list gatsby): V1 & V2
  • Operating System: OS X
bug

Most helpful comment

Update:

Our first iteration of accessible client-side routing has been released! Because this issue goes so far back and it's taken us a while to create a deliverable, I'd like to spend some time highlighting the work that went into this improvement, roadblocks that came up during development, and our hopes for future iterations.

With #19290, Gatsby announces routing updates using a live region embedded within our Router. This behavior is confirmed for the following technologies:

  • NVDA + Firefox
  • NVDA + Chrome
  • Safari + VoiceOver
  • Chrome + VoiceOver
  • NVDA + IE11
  • Narrator + Edge
  • Safari + VoiceOver (iOS)
  • Chrome + TalkBack (Android)
    NOTE: Talkback reliably announces each update twice. We were able to minimize bugs in all other testing conditions, but this one seems potentially unfixable. Please let us know if you have a solution.

Work so far

The largest effort in implementing this improvement was in the research phase. @marcysutton has written about her efforts in the comments above, but I'll summarize here as well. Gatsby worked closely with Fable Labs in July 2019 to gather feedback from users with disabilities on a set of prototypes with navigation techniques for JavaScript web apps. You can read more in this blog post, which was updated just recently (January 2020). Based on the July 2019 testing and additional testing done at the Inclusive Design 24 Virtual Conference in October 2019, it was determined that we should:

  • manage focus on page change #13197
  • add an ARIA Live Region to announce location on page load #19290

I was hired in October 2019 to pick up these efforts. Additional constraints applied to this project, as implementing these changes in Gatsby core is different than implementing them in "userland." This was evidenced by differences in behavior between Marcy's prototypes that handled routing themselves and testing sites using Gatsby's core routing.

Once the MVP was created, extensive testing was conducted for as many combinations of free tooling as possible.

Next steps

It's clear that our work is not done here. I want to keep iterating and keep improvin go we can raise the baseline accessibility that each Gatsby site ships with by default.

In order for this issue to not continually expand, we ask that you create new issues for scoped chunks of work (e.g. If you feel especially strongly about Gatsby having an option to turn off client side routing, creating a new issue for that would be fantastic and would help us gauge impact). I'm already creating issues for:

  • Localization of "Navigated to" announcement
  • Adding configuration options for focus and announcements instead of programmatically inferring what should happen on page change (e.g. allowing developers to specify a component or heading that should signify a page change has occurred)

Thank you all for caring so much about this and helping us make the web better for everyone, one incremental improvement at a time. ❤️ 🚀

All 36 comments

Thanks so much for putting this together!

I'm going to put together the research on the possible options we can go down (that you mentioned).

This will include how they perform when using the most commonly used assistive technologies.

Update:
As far as I know, this is a fairly well documented problem but no projects attempt to fix it by default.

This means it requires pre-existing knowledge of the issue, so the majority of projects are broken.

Ember.js community have a great add-on: https://github.com/ember-a11y/ember-a11y.

While this is not very scientific, if we check how often this is downloaded compared to the ember-cli package, you can see how many people actually opt-in to this.

127 downloads vs 85k downloads
http://www.npmtrends.com/ember-a11y-vs-ember-cli

I believe this issue is related to why Safari reader view—both macOS and iOS—gets stuck to the blog post you were previously on.

Sometimes the reader button doesn’t show up at all, for example when going from /blog/ to /blog/post/, likely because the browser doesn’t fully know the page has changed.

Gif showing the bug:
safari reader mode

/cc @ryanflorence

In #5533, @jhackett1 linked to this article, which offers up an idea for React Router: https://medium.com/@robdel12/single-page-apps-routers-are-broken-255daa310cf

I'd also be curious to hear if any SPA best practices have already been vetted by the a11y pros out there: if you've got time, @marcysutton @LJWatson, I'd love to get your perspective.

Thanks @jlengstorf for the ping.

The trick is to mimic the default behaviour of the browser (and therefore the AT) as closely as possible, and to adopt good practices for authoring content to support this behaviour.

When a conventional page loads in the browser, keyboard focus is returned to the top of the viewport. This enables keyboard users (whether they use an AT or not), to explore the page from the top using whatever techniques they're used to. It also causes screen readers to announce the content of the <title> element - the first indication a blind person has that they've reached their intended destination.

It is good practice to give each page in a website/webapp a unique title that concisely describes the primary purpose of the content. It's also useful if the page title is reflected in an <h1> heading at the start of the main content area.

To make this happen in an SPA you need to fake it: use JS to take keyboard focus to the <body> element (or nearest sensible container) when the view is replaced/updated, replace the <title> element with something unique to the current SPA view, and update the <h1> (assuming it's there) at the start of the main content area accordingly.

Thanks so much, @LJWatson!

Since Gatsby won't be able to control the <title> or <h1> parts of the app, we'll have to focus on what we can:

  1. Let's add a11y docs to talk about best practices (#5592)
  2. We can (hopefully) patch gatsby-link to focus on the Gatsby container div after page transitions

I just ran a local test that appears to confirm page navigation works as expected after adding a step that focuses the ___gatsby wrapper after navigation. Will open a PR shortly.

One thing I should have mentioned, is that when a conventional page is loaded and keyboard focus is returned to the top of the viewport, screen readers will automatically announce the <title> element and then start to read the content of the page. When faking it for an SPA, the content of the page may not be automatically read by the screen reader - but this is ok, because focus is in the expected place and the user can explore the content for themselves from this logical starting point. Worth mentioning in case it comes up when you test your fix.

Thanks @LJWatson! I was able to confirm with VoiceOver that the fix I'm proposing in #5593 causes the page title to be announced. If you don't mind, once it's merged I'd love to send you a link for confirmation that it's working as you'd expect.

@LJWatson I had wondered what users do when faced with no update, is it likely any users would consider this a barrier? Would you say your knowledge of the web helps you understand what has happened, or is this something reasonable to expect most users to figure out?

@jlengstorf No problem.

@nickcolley
My hunch is that most screen reader users wil think the link (or whatever they activated) is broken, because to all intents and purposes nothing will have happened. Focus will remain on the link and/or be cut loose if the link isn't present in the new view and the page title won't have been announced. Unless they choose to go exploring, people are likely to be oblivious at worst and very confused at best I think. Understanding development certainly helps me figure these things out for sure though!

Fixed with @reach/router in v2

I think we need to reopen this, as I'm not hearing page changes announced on my website or gatsbyjs.org now that @reach/router is in place. The resetting of focus to the top of the page works fine, but I don't hear anything announced when I navigate through top-level pages in Voiceover with Safari and Chrome, IE11 and JAWS, or Edge and NVDA. I can open an issue with Reach Router as well, but rather than open a new Gatsby issue I thought I'd keep the discussion here. It seems like we need to do more to communicate to screen reader users here (and hence tweets like this one).

One odd thing with NVDA or JAWS on gatsbyjs.org is when I navigate to /docs, I hear probably the aria-current value for link for some reason, like it's rendering before focus is reset to the top of the page. But it only happens on /docs.

Can someone test a bit with one of the above screen readers as well to make sure I'm not imagining this regression? :)

Update: I'll do some more testing of my own once the CSUN Assistive Tech conference is over, but I'm also doing prototype testing with people with disabilities to try and establish a best practice: https://marcysutton.com/prototype-testing-accessible-clientside-routing/

@marcysutton thanks Marcy, let me know if you need any help!

Hiya!

This issue has gone quiet. Spooky quiet. 👻

We get a lot of issues, so we currently close issues after 30 days of inactivity. It’s been at least 20 days since the last update here.

If we missed this issue or if you want to keep it open, please reply here. You can also add the label "not stale" to keep this issue open!

Thanks for being a part of the Gatsby community! 💪💜

Not stale, just juggling priorities. This is still in progress

@marcysutton Can confirm not hearing page changes announced on MacOS with VoiceOver in Chrome/Safari.

@marcysutton Any update on this? Just finished my first Gatsby site and really don't feel comfortable releasing it with such a huge accessibility hole. Safari/Voiceover is not managing page change focus at all for Links

@kencoxdesign yes! I've been conducting user testing on accessible client-side routing for the past month, determining the best way forward to serve people with disabilities beyond screen reader usage only. The latest code development is that we have #13197 to merge, which will make an incremental improvement to at least announce page changes in assistive technology–but we want to improve this even further to handle focus and announcements in a way that better supports keyboard users and screen magnification. So stay tuned for more on that front.

This issue can be closed with the merge of #13197! But this effort is not over yet–stay tuned for more information about what I uncovered with user testing and how to make routing even more accessible.

@marcysutton I'm still not hearing page change announcements with Gatsby 2.13.2 using Voiceover in Safari on MacOS in @pieh's and my gatsby-starter-default example.

@mattcdowning bummer! I can reproduce that now with an upgraded test project, too. I tested this more fully before to support assistive technology beyond Chrome and Voiceover, and I'm not sure where it went wrong. I'll reopen the issue for now since it still isn't addressed, and needs to be. Thanks for the heads up.

To follow up on this: we are investigating and working on some improvements. Stay tuned.

Alright folks, here's what I have to report:

This issue isn't actually Gatsby-specific: it stems from @reach/router not providing consistent announcements when a wrapper DIV is focused (Test case in a Codesandbox). The wrapper element currently has role="group" and tabindex="-1", but Voiceover needs an aria-label to expose an accessible name and NVDA wants a different role: "status" and "application" are two working candidates right now, but neither of these solutions are particularly great. I could possibly live with role="status" to get announcements working in NVDA, but there are likely side-effects in Talkback and we'd want to test it thoroughly. role="application" is an absolute no-go as it would sabotage the screen reader experience for all Gatsby sites and isn't worth it in my opinion.

Focusing on a heading in the newly changed content area would be fantastic but it's in the realm of "userland" where we can't control what goes in there. Hence the idea to focus on a wrapper element. It's worth adding that this wrapper focus approach is intended to be a _fallback method_ as more component-driven approach is introduced that provides better functionality and access to a range of users with disabilities. For more on that approach, my blog post is up in PR form now (and open to comments). https://github.com/gatsbyjs/gatsby/pull/15579

@marcysutton I loved that blog post. It really has me thinking about how a button could be added programmatically to receive focus. I wonder if it would be distracting or too repetitive if the button's label was automatically informed by the first heading within the new content (if one existed)?

@kishba that's sort-of what I was thinking–using aria-labelledby pointed to a nearby heading to label it. But we'll need some more testing on that part. Once I've got a solution working I'll circulate it to get some input from users.

@marcysutton Great blog post, and I'm glad your testing also caught the aria-current issue. I was already looking into what it would take to add that myself. Would need to add aria-current under the same conditions that the existing css class is added to visually highlight the currently active link.

Is it possible to disabled frontend router to keep a standard way of loading pages ? I think it's the safest way for everyone to access the content.

Client side routers re-implement navigation between pages (views) in the browser.
Assistive technologies rely on this browser behaviour to announce important information such as:

  • when a page has loaded
  • a summary of what can be interacted with (landmarks, headings etc)
  • if a page is taking a while to load it will indicate the progress

It also will:

  • set focus to the document
  • allow for ‘backwards-forwards’ caching of pages
  • allow for streaming the page to the browser before it’s fully rendered

I think that Gatsby should take a step back and review the user needs that led them to making this decision in the first place.

For example, one of the drivers for client side routing is for fast page transitions, this can be achieved with better caching via Service Workers.

This way users can get really quick pages while getting the benefits of full page refreshes that I have listed above.

To quote the author of the client side routing library used in this project:

"There’s a chance I believe client side routing on the web is usually not preferred. Which is ironic.
Might be best for screens where the majority of the UI persists, which is the edge case.
Browsers handle page transitions really well.
Still working through my thoughts 🤔"

– Ryan Florence tweet

I agree that the speed of loading is a positive point to work on. However, there are accessibility laws in Europe (and in other countries) that cannot be satisfied by the current functioning and that prevent us from using the product.

This way users can get really quick pages while getting the benefits of full page refreshes that I have listed above.

To be honest I personally would prefer standard navigation anyway. Client side routing without a signifier (vanilla routing has this by adding the loading spinner in the tab, client side routing needs transitions) can be confusing ("did something actually happen?").

Wasn't there some research that suggested that some actions can be too fast i.e. users should have to wait a minimal amount of time otherwise they don't recognize that an action happened?

See https://github.com/gatsbyjs/gatsby/pull/19290 for our current research into accessible routing in Gatsby.

Update:

Our first iteration of accessible client-side routing has been released! Because this issue goes so far back and it's taken us a while to create a deliverable, I'd like to spend some time highlighting the work that went into this improvement, roadblocks that came up during development, and our hopes for future iterations.

With #19290, Gatsby announces routing updates using a live region embedded within our Router. This behavior is confirmed for the following technologies:

  • NVDA + Firefox
  • NVDA + Chrome
  • Safari + VoiceOver
  • Chrome + VoiceOver
  • NVDA + IE11
  • Narrator + Edge
  • Safari + VoiceOver (iOS)
  • Chrome + TalkBack (Android)
    NOTE: Talkback reliably announces each update twice. We were able to minimize bugs in all other testing conditions, but this one seems potentially unfixable. Please let us know if you have a solution.

Work so far

The largest effort in implementing this improvement was in the research phase. @marcysutton has written about her efforts in the comments above, but I'll summarize here as well. Gatsby worked closely with Fable Labs in July 2019 to gather feedback from users with disabilities on a set of prototypes with navigation techniques for JavaScript web apps. You can read more in this blog post, which was updated just recently (January 2020). Based on the July 2019 testing and additional testing done at the Inclusive Design 24 Virtual Conference in October 2019, it was determined that we should:

  • manage focus on page change #13197
  • add an ARIA Live Region to announce location on page load #19290

I was hired in October 2019 to pick up these efforts. Additional constraints applied to this project, as implementing these changes in Gatsby core is different than implementing them in "userland." This was evidenced by differences in behavior between Marcy's prototypes that handled routing themselves and testing sites using Gatsby's core routing.

Once the MVP was created, extensive testing was conducted for as many combinations of free tooling as possible.

Next steps

It's clear that our work is not done here. I want to keep iterating and keep improvin go we can raise the baseline accessibility that each Gatsby site ships with by default.

In order for this issue to not continually expand, we ask that you create new issues for scoped chunks of work (e.g. If you feel especially strongly about Gatsby having an option to turn off client side routing, creating a new issue for that would be fantastic and would help us gauge impact). I'm already creating issues for:

  • Localization of "Navigated to" announcement
  • Adding configuration options for focus and announcements instead of programmatically inferring what should happen on page change (e.g. allowing developers to specify a component or heading that should signify a page change has occurred)

Thank you all for caring so much about this and helping us make the web better for everyone, one incremental improvement at a time. ❤️ 🚀

@madalynrose awesome work ❤️ great to see this shipped 👏

Thanks so much for this work, this is groundbreaking work to make SPAs accessible in general. I can't believe it's only happening just now. We're building a router for Svelte and closely watching this work. If we can contribute back we will.

Following up here to to ask a specific question of you, @PigeardSylvain, since I'm digging deeper into success criteria for modern, JavaScript web applications. You said:

there are accessibility laws in Europe (and in other countries) that cannot be satisfied by the current functioning and that prevent us from using the product.

Improvements have shipped since then, and I'm familiar with the EU Accessibility Directive and how it relates to WCAG. But I still wanted to ask if there are specific regulations, guidelines, or success criteria that prevented you from using Gatsby, so we can do our best to improve access and compliance. I have some ideas, but in general there aren't currently requirements for user problems related to client-side routing in WCAG. So I'd love to hear more from your perspective (and anyone else with relevant input).

Was this page helpful?
0 / 5 - 0 ratings