-
-
Notifications
You must be signed in to change notification settings - Fork 10.4k
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
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
SPA Accessibility - focus not reset when route changed #5210
Comments
Just to make sure that we're on the same page, you're referring to the same thing as this article discusses, correct? https://sarbbottam.github.io/blog/2016/10/22/focus-reset-and-guided-focus-management Whenever the location changes, you want to reset the focus to some initial position (like a full page refresh would produce). I think that what you would want to do is to have a root component (inside of your class Refocus extends React.Component {
componentDidUpdate() {
if (this.node) {
this.node.focus();
}
}
render() {
return <div ref={n => this.node = n} tabIndex={-1}>{this.props.children}</div>;
}
}
// usage
ReactDOM.render((
<BrowserRouter>
<Refocus>
<App />
</Refocus>
</BrowserRouter>
), holder); If I'm completely off, please let me know. |
Yes focus management for SPAs using react is what I am looking for, thanks for the suggestion @pshrmn! I did some extra digging myself, Angular seems to focus on the first h1 on a client side change, which is a pretty nice user experience. Testing the problem, the issue is not just the focus management but letting an assistive technology (screen reader) user know when a page has changed. My current solution: An aria-live div at the top of page (only re-renders on server side) which will announce to AT when I change its contents. (Visually hidden class, non-focusable) <div aria-live="polite" className="gs-u-vh" id="accessibility-message"></div> Then to control the contents of the aria-live div, I have a function that changes its contents when my h1 changes. (As the aria-live div is not re-rendered client side, it will announce its new contents) export function pageChangeAnnouncement() {
let accessibilityHookMessage;
let myH1;
if (typeof window !== 'undefined') {
myH1 = document.getElementsByTagName("h1")[0].innerHTML;
accessibilityHookMessage = `${myH1} , View loaded`;
document.getElementById("accessibility-message").innerHTML = accessibilityHookMessage;
} else {
accessibilityHookMessage = `page change, View loaded`;
}
} I appreciate your suggestion @pshrmn, it sent my down the right path but when I tested it, it did not behave as expected. This way does look a bit old school JS, but it works. let ignoreFirstLoad = true;
if (ignoreFirstLoad) {
ignoreFirstLoad = false;
} else {
let resetFocusItem = document.getElementsByTagName("h1")[0];
resetFocusItem.setAttribute("tabindex", "-1");
resetFocusItem.style.outline = "none";
resetFocusItem.focus();
} A few notes to anyone else trying to manage focus:
|
This seems like a great idea, but not working for me for some reason. I have tried a number of variations of focus, blur, etc. and it doesn't seem to be taking effect. I can see that the |
Nevermind. My problem was that I was doing it in |
I'd like to see more emphasis on an easy-to-use focus management solution in React Router instead of a live region, because functionally it is helpful to put the user's focus in a specific part of the page rather than leaving it where it is (on a Router link). I can use an The problem with focusing automatically when a component renders is you might be focusing at the wrong time, especially if components get reused. Setting focus when the user initiates the action by activating a link is a better approach, I've found. |
@marcysutton I agree, setting focus when the user initiates an action seems the correct approach. In my approach, I reset focus to the h1 when the page changes and announce the new page name. |
@LpmRaven are you still using the h1 solution and being explicit with regards to the action? |
@jimthedev still using this, I haven't seen any other solutions since opening this issue. If anyone has any new information it would be great to hear. |
@LpmRaven Hi, could you point me to information or explain shortly your advice about focus managment please?
What would be a good focus target instead? I think you are using a heading inside the landmark, but are not sure about it? Why is a landmark a bad idea (edit: Maybe you mean the iOS bug, where dynamic content inside a focussed landmark is not re-read)? I tried out NVDA briefly, and focussing on a heading element seemed OK, but I am not a screen reader user.. |
In the land of Ember we have an addon called Ember A11y, which on route change focuses the browser on a div that wraps the main content (There is more to it but that's the gist). Example: <div class="ember-view focusing-outlet" tabindex="-1" role="group">
<h1>About Page</h1>
...
</div> As I just started really digging in to React I am hoping to find or create a similar solution as it works rather nicely for the Ember apps I work on. |
A Whether this exists as part of core React Router DOM is mostly up to @mjackson. Arguments for including it in RRD would be to extend its reach (:wink:). Arguments for having it in its own package would be that it isn't core functionality and that different packages could choose different implementations (render-invoked props to pass |
My solution (simplified example): class App extends React.Component {
constructor(props) {
super(props)
this.sectionFocusEl = React.createRef()
}
componentDidUpdate(prevProps) {
// https://stackoverflow.com/a/44410281/358804
if (this.props.location.pathname !== prevProps.location.pathname) {
this.sectionFocusEl.focus()
}
}
render() {
return <div ref={this.sectionFocusEl}/>;
}
}
export default withRouter(App) |
Here is a scenario we need to reset focus and currently don't have proper and generic solution to reset focus. |
I'm very keen to include something like this in core @pshrmn. I've been focusing a lot of work on other areas, so I haven't had a bunch of time to really think about this yet. But I'm open to suggestions. Your Is there any consensus yet on what a good solution looks like? |
Just ran into this, and I agree @mjackson - I'd love to see this in core. Setting these sort of priorities sends the right message: a11y isn't optional 👍 I'll try the PR, and report back if I have issues. Awesome work y'all!!! |
I noticed @gatsbyjs have switched their router to reach/router because it supports screen readers on a SPA, progress! It's great that accessibility is being put at the forefront of some prominent js projects. "Without the help of a router, managing focus on route transitions requires a lot effort and knowledge on your part. Reach Router provides out-of-the-box focus management so your apps are significantly more accessible without you breaking a sweat." Great stuff! |
In v4.3 I have the opposite problem. When I change the option from the menu the focus resets. Just to clarify: I'm using Before to use the router I was able to change the selected option from the arrow keys. |
For people looking for a solution for v4.x, I wrote a thing that might help: https://github.com/oaf-project/oaf-react-router |
I am not experiencing a focus change using React-router-dom 5.1.2. @i5ar If you are using the keyboard, you can press alt+down arrow to navigate through the combobox without focusing the page. You can also just have a list of page names, and perform a redirect when the user selects a page. |
@frastlin I still don't think this issue has been fixed. Its been 3 years. I would hope to see something similar to reach/router (which I now use). Marcy Sutton (at GatsbyJS) did some great work last year, user testing of accessible client-side routing techniques. I would suggest everyone read that blog post. |
That article is amazing. If there is a way for users to specify an element to target, with updated content as a fallback, that would be ideal. Now, when a screen reader user clicks on a link, nothing happens, which violates a fundamental design principle. reach/router is being slowly deprecated as seen in: So this needs to be fixed in react-router now. |
@frastlin I think there needs to be consistency across websites as to where the focus moves on route change which is why this is important to be implemented by the router rather than custom code (which I was initially trying to do in this issue). I hope this will be fixed soon, it does mention this issue on in the features list: Whoever implements it should be referring to @marcysutton 's article (in my previous comment). If anyone finds anymore tested research on this issue I would appreciate hearing about it. |
I've been hesitant to make any recommendations here since I don't want to cause more harm than good. I'm grateful for the pioneering work that was done in reach/router, but according to Marcy's article the approach it takes is just the beginning of a really comprehensive solution. It seems like the "best practice" for managing focus with a client-side router continues to evolve (the last update to the article was less than 4 months ago). From that article:
She also says:
This reminds me of the approach taken by @pshrmn in #6449. It's not an automatic solution, so people are free to disregard it entirely. But at least it provides a starting point. Maybe if we did have a |
Well now we have nothing, so anything is better than what we have now, as long as it doesn't significantly break. |
Here's a variation of @pshrmn's refocus component, using hooks and TypeScript: let prevPathName: string | null = null
const FocusOnRouteChange: React.FC = ({ children }) => {
const history = useHistory()
const ref = useRef<HTMLDivElement>(null)
history.listen(({ pathname }) => {
// don't refocus if only the query params/hash have changed
if (pathname !== prevPathName) {
ref.current?.focus()
// prevent jank if focusing causes page to scroll
window.scrollTo(0, 0)
prevPathName = pathname
}
})
return (
<div ref={ref} tabIndex={-1} style={{ outline: 'none' }}>
{children}
</div>
)
} |
@lionel-rowe thanks for the hook, I had to wrap the history listen inside a useEffect hook. |
It's a bummer that React Router still doesn't handle focus at all. Is that going to be addressed soon? Sending focus to a wrapper element or a heading would be better than doing nothing, even if a comprehensive skip link solution isn't in the cards right now. |
@marcysutton from what I've just read online: https://reacttraining.com/blog/reach-react-router-future/ The article is from 2019 though, so I wouldn't get your hopes up. Maybe we can help with this? |
That post is from May 2019, and it's almost 2021–hence my comment. Given @ryanflorence's past commitment to accessibility including Reach UI and Reach Router, I'd hope the React Training team in its new rendition could meet this requirement and not leave it to the community to handle. It otherwise doesn't send a great signal to the community that accessibility is being taken seriously at all. |
For those looking for a solution until this ticket is closed, the article "Accessible page title in a single-page React application" by Kitty Giraudel describes a really good solution based on React Router and React Helmet. |
This is what I ended up doing (inspired by Kitty's article). const ref = useRef<HTMLSpanElement>(null);
const { pathname } = useLocation();
// remove once react-router accessibility issue is fixed
// https://github.com/ReactTraining/react-router/issues/5210
useEffect(() => {
ref.current?.focus();
}, [pathname]);
return (
<>
<span ref={ref} tabIndex={-1} />
<a href="#navigation" className={className}>
Go to navigation
</a>
<a href="#main" className={className}>
Go to content
</a>
</>
)
|
Looking to revive this and get some default focus management on the table. Lots of great progress has been made in this area since the issue was first opened, much of it from contributors to this thread, and more than enough for us to act. I think we can offer a reasonable default in both v5 and v6 in the coming weeks. I'll follow up here with a few proposals for those interested in offering feedback. |
This thread came up in my search for a solution to the same issue with Angular.
|
I'm going to convert this to a discussion so it can go through our new Open Development process. Please upvote the new Proposal if you'd like to see this considered! |
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
I am using a screen reader to interact with my application.
Moving between routes does not reset the focus, which would occur on a server-side render. Is there a fix to reset the focus on route changes?
The text was updated successfully, but these errors were encountered: