I am making a page with tabs. Tab header uses Links, and the tab contents have different nested routes. On tab click the page jumps to the tab container. Expected behavior: page stands still in the same position it was before clicking. I was using react router previously and the page would stay still in the same setup. Example: https://codesandbox.io/s/y0jmjj2o8j
Or am I using it in a wrong way?
Also have the same
I think this is because Reach automatically focuses the route navigated to (for accessibility reasons), and browsers try to scroll to focused elements. If you want to maintain the scroll position I imagine you'll have to manage that yourself
The question is, should it be the default behaviour?
Scrolling to a focused element is standard browser behaviour, so probably
Ok, I'll re-formulate the question: should it be the router's job to focus on the element? When is it good for user experience? I'd expect this to happen in case of hash links, not normal routes.
As far as I understand one of Ryan's explicit goals with the Reach ecosystem was to make everything as accessible as possible. Moving the user's focus to the newly "navigated" to element is an important part of accessibility for a client-side router.
I believe it's an attempt to re-create what happens when you visit a server-side route (e.g. click a normal link). A new page loads and focus is moved to the top of it, allowing the user to tab through the new content. Screenreaders will also announce the title of the new page.
In most other client-side routers focus generally won't move, which means when the user clicks a Link the only indication that any "navigation" happens is visual. For example a screenreader user will have no idea anything on the page changed. A keyboard user will be stuck focused wherever the Link was, or worse (if the Link un-rendered) they'll have no idea where their focus is.
I understand that this behaviour feels weird and unexpected (it was jarring for me too when I saw it), but the reason for that is all the other client-side routers not correctly replicating the behaviour/accessibility of server-side routing.
I believe you can use the primary prop on the Router component to achieve what you desire.
<Router primary={false}>
See the following docs link:
@niccai that's a workaround, but not a solution to this problem. If you change the route of your application and all the content is new, you want to change the focus so keyboard users ad screen readers are placed in the correct place to consume the new content. If you set primary={false} on a page change or major content change, you're hurting the accessibility of your site.
It would be nice to have an option on a route that would make it behave in the conventional way, as if the page reloaded. I tried to add a scroll-up on certain url changes, but haven't figured out yet why it works in some cases but not others. I really did not want to go back to react router but maybe that's a better option for my use-cases
@simoroshka are you saying at the path level being able to designate if the component that is going to be rendered should be focused on or not?
Yes, something like <Route path="subpage" focus={false}/>
In what situation would you want to opt out of keyboard/screen reader users being placed in the correct place? Where would you expect focus to be after such a navigation? The body?
focus has an option to deal with this:
element.focus({
preventScroll: true | false
});
But it would be nice to have a consistent API:
<MyRoute path="hello" preventScroll={true} />
<Link to="somewhere" preventScroll={true}>Link</Link>
<Redirect to="no-way" preventScroll={true} />
// these are ugh, better ideas?
navigate("/somewhere", { newId: id }, true | { preventScroll: true })
redirectTo("/no-way", true | { preventScroll: true })
It's a bit tricker than it seems at first, need conflict and precedence resolution. E.g if route has preventScroll={true}, all other APIs should be able to overwrite it by passing false and _vice versa_.
Preventing focus altogether is an alternative (@simoroshka comment) but same thing, all APIs. Might even be nice to add both at the same time, I see use cases for both. But too much stuff for this router?
What does @ryanflorence think about any of this?
Accessibility is definitely a good aim but autofocus and
<Router primary={false}>
is somewhat confusing in terms of an API.
Focus automatically scrolls the page, you'll need to manage scroll yourself, or put primary=false on router and then you should be managing focus yourself.
We have plans to manage both for apps, but we need Suspense from React to mature before we can do it all reliably.
Focus and scroll management are high priority as we move both React Router and this project toward the same hook-based API. More info: https://reacttraining.com/blog/reach-react-router-future/
Most helpful comment
I believe you can use the primary prop on the Router component to achieve what you desire.
<Router primary={false}>See the following docs link:
https://reach.tech/router/api/Router