-
Notifications
You must be signed in to change notification settings - Fork 26
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
Focus management for SPA navs #190
Comments
Thanks for this writeup, @domenic! There are some really cool ideas here. Resetting focus to the While |
Hmm, I don't quite understand the distinction you're making. Let's say I have a web app. I think I have two options for desired user experience:
What I'm proposing is that, whichever of these two experiences you choose, you get the same result whether your app is an MPA or a SPA:
I.e., there is complete symmetry between MPA navs and SPA navs. You seem to be calling for some _a_symmetry, which I don't really understand. When the user loads page B, either via reload or link click or navigating directly, either the |
I understand #190 (comment) as a distinction between first page load and subsequent navs for SPAs. When a user first visits a SPA (pasting URL or linked from an external site), they might want to start from the top of the page to familiarize themselves, maybe with the links in a navbar. But when the user clicks an internal site link, the SPA can try to offer a more efficient UX by moving focus within the page (since the user has already been through the navbar once). Note that Gmail does not currently follow this pattern. For the inbox, both first page load and internal links move focus to the first email. But Twitter does, first page load sets focus all the way at the top of the DOM. If you TAB to a Tweet and navigate to it, then SHIFT+TAB to their back button, focus is at the top of your feed (skipping messages & left side nav).
|
@domenic I agree with the end-goal you stated in https://groups.google.com/a/chromium.org/g/chromium-accessibility/c/wDUwToU8LdM:
Would this be accomplished by |
I am still worried about SPA focus restoration on back/forward navs. If focus is somewhere besides
document.documentElement.addEventListener("beforepagerestoration", e => {
if (!everythingIsLoadedAndClientSideRendered()) {
e.preventDefault();
setClientSideRenderingFinishedCallback(() => doPageRestoration(e));
}
});
function doPageRestoration(event) {
const { scrollPoint, sequentialFocusStartingPoint } = event.getDestination();
if (!shouldStillDoPageRestoration(scrollPoint, sequentialFocusStartingPoint)) {
// Don't do any restoration if something dramatic has changed,
// e.g. if a bunch of files were deleted and we're going back to the
// file list view. Maybe scrollLeft/scrollTop could help with this logic.
return;
}
event.restore();
} |
This thread, and the proposed after-transition/immediate/manual enum, is about focus management. Announcement to ATs is actually a very separate issue.
Well, in the SPA case, I think both this and #187 already have some good hooks: you'd call them during the appropriate point in the navigate event, using app history. The |
I've specced out a minimal version of this, with only |
Got pulled into a project, just coming back to this, but just wanted to say awesome job with that PR @domenic — so excited for this work to land! |
This is spinning off from #25. It was also briefly mentioned in #162.
We have consistent feedback that focus management during SPA navs is an accessibility concern. This writeup goes into good detail.
I think we should make sure that by default, same-document navigations that are controlled by app history (i.e., using
navigateEvent.transitionWhile()
) give a good focus experience. The starting point here is to get parity with cross-document navs (MPA navs). This means:<body>
element, or the firstautofocus=""
element if there is one.https://boom-bath.glitch.me/focus-navigation-test.html is a useful test page for this.
For traverse navigations, if the page is stored in the bfcache, then the current plan per whatwg/html#5878 / whatwg/html#6696 is to restore focus. I.e., since the entire
Document
is kept around, we just make sure not to clear its focus state when transitioning into or out of bfcache.But for traverse navigations where the result is not coming from bfcache, there is no attempt at focus restoration in current browsers. I.e. there is nothing like scroll restoration (cf. #187) which tries to somehow find the right element to focus in this newly-created from-network-or-cache
Document
. It just does the usual<body>
-or-autofocus=""
dance (andautofocus=""
doesn't seem to work in Chrome...).So what should we do for app history and traverse navigations?
<input>
element inside the#nearest-ancestor-with-id
container and on restore try to focus based on that pointer". This seems really brittle and unpredictable.My inclination then is to treat traverse navigations the same as push/replace/reload, and reset focus to the body or
autofocus=""
element by default when using app history. Anything more complicated pretty much needs to be done via manualfocus()
calls, IMO.OK, so what does this all mean for the API? I think it means the following:
navigateEvent.transitionWhile(promise, { focusReset: "after-transition" })
, this waits forpromise
to settle and then resets focus to the<body>
element or the firstautofocus=""
element.navigateEvent.transitionWhile(promise, { focusReset: "manual" })
, app history does nothing with focus, i.e. the probably-now-offscreen element stays focused, or if it gets removed from the DOM/madedisplay: none
then focus resets to<body>
whenever that removal happens. (Note: this "focus fixup" ignoresautofocus=""
elements.)navigateEvent.transitionWhile(promise, { focusReset: "immediate" })
, this immediately resets focus to the<body>
element or the firstautofocus=""
element.Which of these should be the default? I think
"after-transition"
is probably best, specifically because of theautofocus=""
interaction. We want to do this focus reset whenever all the elements, including the potential autofocus target, are ready. This does lead to a potentially-problematic situation where the focus stays on an obstructed element (e.g. behind a loading spinner), but I think more likely focus would get reset to<body>
due to element removal or hiding at some point, and then either stay there or get moved to theautofocus=""
control once things are loaded.Overall this proposal is OK, but it does require some manual work by developers: mostly, putting
autofocus=""
on appropriate places. In particular per the research cited above, screen reader users preferred resetting focus to a heading or to a wrapper element, instead of to the<body>
. To achieve that, we need help from developers to tell us what element focus should go to, and that's whatautofocus=""
provides.The text was updated successfully, but these errors were encountered: