vuejs / vue-router

🚦 The official router for Vue 2
http://v3.router.vuejs.org/
MIT License
18.99k stars 5.06k forks source link

Improve vue-router's screen reader experience #2488

Open marcus-herrmann opened 5 years ago

marcus-herrmann commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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

thedamon commented 5 years ago

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

danielnixon commented 5 years ago

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 commented 5 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 3 years ago

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

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 commented 2 years ago

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()
                })
            })