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

Improve vue-router's screen reader experience #2488

Open
marcus-herrmann opened this issue Nov 19, 2018 · 13 comments
Open

Improve vue-router's screen reader experience #2488

marcus-herrmann opened this issue Nov 19, 2018 · 13 comments
Labels
discussion group[a11y] Issues regarding accessibility and how to improve overall default experience for screen reader users
Projects

Comments

@marcus-herrmann
Copy link

marcus-herrmann commented Nov 19, 2018

What problem does this feature solve?

This feature request is about the screen reader experience of apps built with vue-router. In a nutshell: When a user of assistive technology, such as a screen reader, activates a <router-link>, their focus stays on said link, giving them no feedback at all (user with sight don't have that problem because they see that parts of the app change). So it is about notifying user of DOM changes.

One way is to set the focus to the newly loaded content.

General links on this:

My suggestion on this to match Reach Router behavour (which puts, after route transition, the focus on the wrapper of the loaded components. In my understanding the vue-router counterpart for this is <router-view>).

You could also go even further by defining a "focus target". I wrote about this approach here: https://marcus.io/blog/accessible-routing-vuejs

What does the proposed API look like?

<router-view focustarget="someRefInChildComponent"></router-view>
// If focustarget is not supplied, focus after route transition is put on router-view itself
@posva
Copy link
Member

posva commented Nov 19, 2018

Thanks for bringing up the discussion. I've been looking at reach router for some time regarding this and it's something we should be able to provide

@tesla3327
Copy link

I think this is a great idea, since I will have to implement this behaviour in order to make our app fully accessible.

Defining a focus-target instead of the default, as well as being able to opt-out of this behaviour completely seem to make sense to me as part of the API.

@marcus-herrmann
Copy link
Author

In this context, it would be great if we could add aria-currentto the active <router-link>. See #2116

@thedamon
Copy link

thedamon commented Dec 3, 2018

I was looking at scrollBehaviour and thinking that it might make sense to tackle focusing on route change in a similar way, say, focusBehaviour.

It becomes a little tricky figuring out how to best handle focusing on elements. Typically, though a safe default would be the wrapper element for the route itself (you just might have to add tabindex="0" to it, possibly programmatically)
Perhaps each route could take either a ref name within that component, or an ID, or fallback to the wrapper element of the route component and router could attempt to move focus there on change?

I'm currently fighting with how to throw focus to a layout component with a router-view because I want to focus on its header on change of its router-view

@vuejs vuejs deleted a comment from AustinGil Dec 4, 2018
@posva posva added this to Design proposals + discussion (high prio, high complex) in Longterm Mar 26, 2019
@posva posva added the group[a11y] Issues regarding accessibility and how to improve overall default experience for screen reader users label Mar 26, 2019
@danielnixon
Copy link

This is something I'm tackling over at https://github.com/oaf-project/oaf-vue-router and https://github.com/oaf-project more generally.

@posva
Copy link
Member

posva commented May 6, 2019

That's cool, I wasn't aware of focus restoration being a common issue with routers. I will also take a look at that. It's nice of you to put some of the links in your repo, I will also be able to check those :)

@mdarrik
Copy link

mdarrik commented Jan 24, 2020

What would need to be done to move this issue forward? This can be a pretty big accessibility problem with Vue-Router, and working around it for every project isn't super ideal. I definitely would like the ability to set the focus based on a ref, as some user research suggests skip links might be one of the best places to deposit focus after navigation (see this talk by Marcy Sutton and Samuel Proulx at Inclusive Design 24 for reference).

I'm not sure if this would be a breaking change (because the focus behavior would be different)? And I assume there'd be some design decisions to consider when the user is handling their focus management outside of vue-router. Would a flag/boolean prop make sense? Like call router view like this <router-view resetFocus="true" :refToFocus="myRef">. Where refToFocus is optional (and maybe makes resetFocus optional if set?).

I'm happy to try and open a PR on this if it doesn't seem like something that needs to wait for a vue-router v4.

@marcus-herrmann
Copy link
Author

marcus-herrmann commented Jan 27, 2020

Now, after a little bit more than a year (and with some user research, conducted by @marcysutton in a Gatsby/React app), I'd like to modify my initial suggestion:

https://marcus.io/blog/improved-accessible-routing-vuejs

tldr;

It's hard to find what to conclude for vue-router in general, except for the focusability on <router-view> (and the other improvments regarding internal anchors and aria-current).

@thedamon
Copy link

Great summary! I just bookmarked your article to read before seeing the response here.

Focusing the app wrapper may cause some issues. In chrome if you throw focus to an element that's larger than the screen, it will just scroll the bottom into view (And this behaviour seems differnet in firefox, safari and IE..
Any focus management will need to take scroll management into account (though that could be on the user). (and without getting too deep into it, focus/scroll options work poorly together with current standards and that could cause issues, so it might be nice to look into that (or just let people fight it out on their own?)

Maybe a separate issue but I do agree skip-links are important to accessibility in general and having them 'just work' in router would go a long way toward encouraging accessible websites

@marcus-herrmann
Copy link
Author

Focusing the app wrapper may cause some issues.

Marcy reached out to me on twitter, I got that part of my research interpretation wrong. Will update the article, soon-ish

Maybe a separate issue but I do agree skip-links are important to accessibility in general and having them 'just work' in router would go a long way toward encouraging accessible websites
👍

@kdmon
Copy link

kdmon commented Oct 14, 2020

Anyone still looking for solutions might be interested in checking out:

In addition to announcing the new page at navigation, it can be used for other events like error messages.

@posva
Copy link
Member

posva commented Jun 21, 2021

Revisiting this and it looks like aria-current is the only feature that could be automatically added by the Router.

  • Focusing the app wrapper after route change -> Should be achieved with a router.afterEach()
  • Working with skip links -> is unrelated to vue router but they can be focused in router.afterEach() if necessary
  • Route title announcement with live region -> Should be achieved with a router.afterEach()

These vary highly depending on the application nature, layout, animations, etc. Having a default in Vue router would break for many of them and become hard to patch.

Instead, I think it would help to have an entry about accessibility in documentation with examples to implement each of these and where any necessary consideration or external resources could be listed.

@daviesdoclc
Copy link

I've done something like this in App.vue. However, on iOS it sometimes doesn't focus correctly. Another element like a button on the page will take focus, but not always. It seems timing dependent. I've even used a setTimeout instead of nextTick and gotten mixed results. So not sure this method is 100% reliable.

            <router-view v-slot="{ Component }" ref="focus" tabindex="-1">
                <component :is="Component" />
            </router-view>
            
            const focus = ref()
            const router = useRouter()

            router.afterEach(() => {
                nextTick(() => {
                    focus.value.$el.focus()
                })
            })

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion group[a11y] Issues regarding accessibility and how to improve overall default experience for screen reader users
Projects
Longterm
Design proposals + discussion (high p...
Development

No branches or pull requests

8 participants