WICG / navigation-api

The new navigation API provides a new interface for navigations and session history, with a focus on single-page application navigations.
https://html.spec.whatwg.org/multipage/nav-history-apis.html#navigation-api
486 stars 26 forks source link

Second thoughts on focus management #202

Closed domenic closed 2 years ago

domenic commented 2 years ago

The current explainer draft describes a new default focus behavior that single-page apps (SPAs) can opt into, by using the new navigation API. Translating the technical details there into an overview:

By default, any [SPA nav that goes through the new navigation API] will cause focus to reset to the <body> element, or to the first element with the autofocus="" attribute set (if there is one). This focus reset will happen after [the developer indicates the navigation has finished]. However, this focus reset will not take place if the user or developer has manually changed focus [during the developer-indicated loading interval]

This default behavior is called the "after-transition" focus reset behavior.

This was motivated by this post by @MarcySutton, which states

... a user’s keyboard focus point may be kept in the same place as where they clicked, which isn’t intuitive. In layouts where the page changes partially to include a deep-linked modal dialog or other view layer, a user’s focus point could be left in an entirely wrong spot on the page.

and by the desire to make the "new default" for SPA navs the same as how MPA navs behave: i.e., to reset focus. The reasoning being, resetting focus to the body (or designated element with autofocus="") would ensure none of the bad outcomes Marcy describes would occur.

However, upon testing some SPAs in the wild, I'm having second thoughts about this. Essentially, resetting the focus by default is hostile to "tab" type patterns. For example:

I think there are many more examples.

So now I'm not sure what to do. Some ideas:

The most important question right now is what the right default should be. We will always have a "manual" mode available. And if we think that some of these clever modes are useful and people might want them in some cases, we can always implement them later. But we need to figure out the default---whether it's "manual", "after-transition", or something more clever---before we can ship an initial version of the navigation API.

posva commented 2 years ago

I would say there are still cases like navbars where you will like the focus to be applied to the content after the navigation. For instance, I personally find it confusing in the case of Twitter to not switch focus to the main content we are visiting (Profile, notifications, etc). You can even mix some of these behaviors in the same app, so none of them would be desirable as a default. Given the mentioned research, it also seems like different users would want different focus patterns, so maybe there should be new aria attributes to be set by the developer on the page, or reuse existing HTML semantic elements like nav and main. Depending on the user settings on the browser, the focus will be set differently.

Otherwise, focusReset: "after-transition" seems like a good default

tbondwilkinson commented 2 years ago

Another possibility: is the currently focused element in the DOM? If so, preserve focus. Otherwise, reset focus. This is simpler and more straightforward than calculating visibility.

Re:navbars setting focus to their contents, I think this is something the app should do, because it knows best what new content the user probably wants to be focused to.

The cases where people move DOM off-page or hide it are often because it's expensive to re-render content, especially if that content was initially rendered on the server. But I think this makes the stronger argument for some browser-native and performant way to cache DOM, because it would make the semantics of focus easier if we knew that whatever was in the DOM was actually part of the page, and not just being cached due to browser limitations.

domenic commented 2 years ago

Another possibility: is the currently focused element in the DOM? If so, preserve focus. Otherwise, reset focus. This is simpler and more straightforward than calculating visibility.

I think this is one of the possibilities I mentioned in the OP: Change the default focus behavior for app history SPA navs, to "manual".

dvoytenko commented 2 years ago

One note: currently a display:none element would drop focus, which is convenient for focus management in a SPA. However, maybe this behavior could also be applied to content-visibility:hidden. After all, does it make sense to keep focus in a content-visibility:hidden DOM subtree?

domenic commented 2 years ago

Examples of cases where "after-transition" is correct, on twitter.com:

So this is an example where on the same page you want both "manual" behaviors (for the sidebar tabs) and "after-transition" behaviors (for other tabs). Hmm.

smaug---- commented 2 years ago

The spec proposal doesn't say anything about focus handling, right? And I think that is expected. The API should expose reasonable events so that authors can then focus whatever element they want. Having some default behavior is likely being wrong in too many cases. (Given how hard it was to have reasonable focus handling for

, as an example, I'd prefer to leave any explicit focus handling out from the initial, at least, version of the navigation API.)

domenic commented 2 years ago

The spec proposal doesn't say anything about focus handling, right?

That's not correct. As detailed in the OP of this thread, and in https://github.com/WICG/navigation-api#focus-management , the proposal attempts to provide better tools for managing focus, to address the accessibility problems with single-page apps outlined in this user research.

So we definitely need to do something. This proposal suggests that "something" is to provide a new mode, focusReset: "after-transition", which works as described in the explainer.

The question at hand in this issue is, what should be the default behavior. The Fable Tech Labs user research claims that current default behavior, of leaving focus on a potentially off-screen element, leads to bad experiences. This issue thread is questioning whether that conclusion holds or not, given counterexamples such as tab UIs.

Having some default behavior is likely being wrong in too many cases.

There's no option where we don't pick a default behavior. The question is picking the right one. The two simplest options are:

The OP also discusses some more complicated possibilities.

MelSumner commented 2 years ago

This Google search displays a carousel of the moons of Jupiter. Clicking on any of them performs an SPA nav. Focus stays on the clicked moon. Resetting focus to the body would be bad here.

I think in this case, the set of images are acting more like a row of pagination buttons/links or even breadcrumbs, on a meta level. See aria-current. The selected link/image could be marked with aria-current="page" and that's the thing that gets an .active CSS class, but it would still be fine to move the focus to the content (search results).

It could be useful to consider a new option: allowing a way to customize the definition of a route change (some existing art) with a specific API that SPAs opt-into (and build into their frameworks). That way they could provide an upgrade path for their users, and help them deprecate the tab-like UIs in a backwards-compatible way.

(Edit to add: I think by adding an API for SPAs to opt-into, it could be possible to otherwise leave focus resetting the way users expect it to work.)

LJWatson commented 2 years ago

There are opposing use cases and all of them are valid, so trying to identify the default behaviour that meets most user expectations most of the time, is probably the best bet.

An advantage of the after-transition focus reset is that it's what most people are likely to expect because it mimics standard page load behaviour, and keyboard users will have navigation strategies based on that expectation.

Sometimes that strategy will be to return to the point on the page where they actually wanted to be, which is where @domenic's use cases come in, and sometimes the strategy will be to navigate through the content as usual.

Setting aside developer effort for a moment, each of the proposed options will be inconvenient to users some of the time and OK for users some of the time.

So my suggestion would be to go with the option that mimics standard page load behaviour, on the basis that it's almost certainly the most common experience users will be used to, but which makes it reasonable for developers to place focus somewhere else if/when there's a valid use case - and I think that's the after-transition focus reset behaviour?

annevk commented 2 years ago

My expectations were similar to those of @smaug----. That focus remains as-is.

I see the argument for resetting by default though.

What happens to selection given https://github.com/whatwg/html/issues/7657?

smaug---- commented 2 years ago

That's not correct. As detailed in the OP of this thread, and in https://github.com/WICG/navigation-api#focus-management , the proposal attempts to provide better tools for managing focus, to address the accessibility problems with single-page apps outlined in this user research.

ok, I'm now lost what text I should be reading. I thought https://wicg.github.io/navigation-api/ is the current proposal and the rest are just random ideas which might be added to it. But I guess this issue is about that - whether to add something to the spec proposal.

domenic commented 2 years ago

The spec draft for focusReset and scrollRestoration is at https://github.com/WICG/navigation-api/pull/201 ; I guess I should merge the focusReset part (which is mostly done) to avoid any confusion, while I try to figure out the scrollRestoration parts.

domenic commented 2 years ago

After some offline discussion, we remain unsure about the correct default, but tentatively resolved to stick with "after-transition". In part, because it better matches cross-document (MPA) navigations; and in part because among the two that choice will more likely encourage developers to think about focus management, because they'll see focus getting reset.

Another interesting idea was to force people into choosing one, by making focusReset a required member. However, that plays very poorly with multiple navigate handlers; it precludes allowing something like a first navigate handler that doesn't care about focusReset behavior and wants to delegate that decision to a second navigate handler.

So for now, we'll close this issue accepting the current behavior. We'll keep an eye out for how people feel about the behavior in the wild and see if there are things to improve. But in the end the worst-case scenario is that we guessed wrong and most router code ends up using { focusReset: "manual "}. That isn't the end of the world.