[Proposal] Focus Traversal API

#1

Focus Traversal API

The current system for programmatically manipulating focus within a web page leaves a lot to be desired. A single element can request the focus with the focus() method but that is the extent of the programatic focus navigation options. Advancing the focus to the next focusable element or the previous focusable element involves a complex dance of DOM traversal and guess work as to what elements can recieve focus or not. Most solutions to manipulating focus are complicated; the very simple need to manipulate focus in a meaningful and accessalbe manner is significantly lacking.

Proposed

The creation of a unified Focus Traversal API that makes understanding, manipulating, and traversing the focus simple. This must include the ability to assign the focus, move the focus forward and backward, and understand both the next and previous focusable elements.

A rough example of this API might be the following:

window.focusManager.currentlyFocused - Contains the element currently holding the focus, if any.

window.focusManager.previouslyFocused - Contains the element that held the focus prior to the current focus, if any.

window.focusManager.history - An array of the last n historical focus holders.

window.focusManager.hasFocus(element) - Returns true if the given element currently has the focus. Functionally equivelent to window.focusManager.currentlyFocused===element.

window.focusManager.focus(element) - Focus on the given element. Functionally the same as element.focus(). Returns void.

window.focusManager.forward() - Move the focus to the next focusable element. Returns void.

window.focusManager.backward() - Move the focus to the previous focusable element. Returns void.

window.focusManager.next(element) - Returns the element that would revieve the focus if window.focusManager.forward() was called when the given element has the focus. If no element is given, the currently focused element is used.

window.focusManager.previous(element) - Returns the element that would revieve the focus if window.focusManager.backward() was called when the given element has the focus. If no element is given, the currently focused element is used.

window.focusManager.orderedElements() - Returns an array of all focusable elements in the order that focus traversal would occur.

Illustrative User Issues

Example Polyfill

An example implementation of the above as well as this document can be found at https://github.com/awesomeeng/FocusTraversalAPI.

Similar Works

https://github.com/davidtheclark/tabbable - A very popular library for getting a list of all the elements in a element that can recieve the focus.

4 Likes
#2

It’s great idea, I had this issue myself.

1 Like
#3

Updated Polyfill to v1.1.0 with support for Shadow DOM traversal.

#4

If a new API is being proposed, why is it synchronous? Is there a technical issue if these return Promises that resolve to their values?

1 Like
#5

Why would it need to be Asynchronous? None of what it does happens asynchronously. See the Polyfill for examples.

#6

I’m just curious as to whether it would have any known implications. I get the feeling going forward web standards are trying to make anything new as asynchronous as possible. That way we can do as much work as possible without blocking the main thread. Simply because the APIs currently available are all synchronous doesn’t mean investigating new ones being async isn’t an option.

1 Like
#7

I currently don’t foresee any type of asynchronous need around focus traversal. focus has always been a synchronous process and I am not proposing changing that in any manner. Focus Traversal as proposed is an addition to existing focus systems, a quality of life improvement without any major impact on the existing focus system. If anyone can foresee any asynchronous need, please do share.

I am also purposely not proposing the addition of any of the higher level ideas around focus such as trapping or focus groups.These are great ideas and I would love to see them, but keeping the proposal simple, clean and very implementable was my first priority.

Glen

#8

The problem is something like orderedElements. This could end up returning a large set of nodes. While the array is being compiled this could block the main thread. So at least this item should be async. Sure a few hundred nodes shouldn’t be a huge issue. But in poorly built systems they could have/end up with multiple thousands on a page.

At least in that case, async makes sense. Which is why I’m bringing up async investigation for the whole API if it moves forwards. Could it be done? What would the drawbacks be in engines to implement that?

I love the idea and I’d personally like to see it brought into browsers. I just want to see some investigation into async if it moves forward so the path doesn’t go untravelled.

1 Like
#9

I can see what you are saying there. The question is do we drop orderedElements entirely or make it an async function? Is there a need for it?

#10

Another use case for it to be synchronous is that when you focus to the “next element” and it happens to be offscreen, the viewport may need to scroll a bit to bring it into view. And the scrolling aspect would make it asynchronous. Scrolling methods are being updated to be asynchronous in the spec btw (I know because I’m the one who requested for it to implemented in browsers, which has since been accepted).

I love this idea btw. :+1:

1 Like
#11

I meant to say “Another use case for it to be asynchronous” – not “synchronous”. But the silly forum settings here won’t let me update the typo in my original comment :roll_eyes:

1 Like
#12

I am beginning to come around on this, but let me ask you this… does calling focus() on an element actually imply that focus will be received by said element or is it just a request. I think the answer to that question dictates whether or not Async is needed for calls like forward() and backward(). If there is a garauntee of focus getting received, then sure, async might be in order. But if it’s more of just a request, then sync is fine.

Also, another thought, but what happens if you call focus() a second time while waiting for the first focus() to resolve? If the resolve condition of focus is the focus event and that never occurs, how would we know?

Glen

#13

Here’s what I’m understanding of HTMLElement.focus() (which could be awry, so grain of salt.)

  1. Check a flag to prevent running focus on an element it is already running on. If true, return to prevent overlap.
  2. Set the flag mentioned previously to true.
  3. Focusing Steps run, which I believe at the end of it all (assuming the element is validly focusable) it forces the element to receive focus.
  4. Unset the flag from step 1 so a new focus call can succeed.

For more info you can see the WhatWG spec on focus requirements. I don’t see how it being required or a request is the linchpin of it being async or not though. The async APIs can always verify the state at the end and return an indicator true/false as to whether the request to focus succeeded or not.

And the flag mentioned in the focus steps clears up what happens if it is called multiple times on an element before one finishes resolving. It just returns and does nothing on subsequent calls.

#14

I think the question of async/sync comes down to whether or not the focusing steps have async behaviors in them (i.e. does any of the focus steps have an async nature). What caught my in in the spec regarding this was this:

User agents must immediately run the focusing steps for a focusable area or browsing context candidate whenever the user attempts to move the focus to candidate.

Which seems to imply a syncronous nature (i think).

#15

Another similar work: https://github.com/medialize/ally.js

1 Like
#16

So what is the next step for moving forward on this? Whom do I need to contact? How does this process work from here?

#17

I think a good next step would be to create a GitHub repository for the proposal and write an explainer for it.

An example: https://github.com/bokand/root-scroller/blob/master/explainer.md

1 Like
#18

Sounds good. I will work on it over he next few days. Some questions:

1). Can I use the current polyfill repo which already has the proposal in it? 2). What do I do once the explainer is done?

Thanks.

Glen

#19

Sure, just an explainer.md file in the project’s root directory should be fine.

Once the document is ready for feedback, don’t hesitate to contact the relevant people directly (e.g., accessibility experts) to get their opinion on the proposed API.

1 Like
#20

Yeah! And dont forget to ping this thread when ready! Excited to see what you come up with!

1 Like