Skip to content
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 meta-bug #4607

Open
5 of 9 tasks
domenic opened this issue May 9, 2019 · 23 comments
Open
5 of 9 tasks

Focus meta-bug #4607

domenic opened this issue May 9, 2019 · 23 comments

Comments

@domenic
Copy link
Member

domenic commented May 9, 2019

Motivated by WICG/webcomponents#762 and #2013, I've realized we need to fix the shaky spec foundations for focus. I've spent the last couple of days canvasing the open issues and trying to come up with a path forward. Here are my results.

Pinging @rniwa, @cdumez, @muan, @tkent-google, @alice, and @bzbarsky as folks who have been involved previously. Feel free to skim/unsubscribe if you're not as interested.

Types of focusability

Previously discussed in #938 and #1786.

There appear to be three types of focusability:

  • Programmatically focusable (.focus(), autofocus="")
  • Click focusable (clicking with a mouse, maybe/hopefully some AT overrides?)
  • Sequentially focusable (tabbing)

Only the first one is script-observable or testable. The others are based on user interaction. But I think the spec should talk about them, as they are important parts of building a browser, and various things the author controls, such as tabindex="" can impact them (at least at a "should" level). Note that the spec already talks about sequential focus navigation.

A lot of different combinations can occur here. Here are some examples showing all combinations I've seen so far. (All use default OS settings.)

Platform Element Programmatically Click Sequentially
All platforms <span> No No No
All platforms <input type="text"> Yes Yes Yes
Windows Firefox/Chrome <span tabindex="-1"> Yes Yes No
Windows Firefox/Chrome Canary <div> with overflowing contents Yes No Yes
macOS Safari/Chrome <area> (with href="") Yes Yes Yes
macOS Safari/Chrome <area> (no href="") No No No
Windows Firefox <area> (either case) Yes No Yes
macOS Safari <input type="checkbox"> Yes No No
macOS Safari with Option held down <input type="checkbox"> Yes No Yes

UAs generally vary wildly on whether something is sequentially or click focusable. This test page (derived from @muan's earlier work) is a playground where you can find out for yourself.

How does this related to today's spec?

Today's spec captures something like this, but in a way that I found hard to understand.

  • It has no concept of click focusable as separate from programmatically focusable. It calls programmatically focusable "focusable".
  • It doesn't have an explicit concept of sequentially focusable. Instead it has a "tabindex focus flag" plus implicit UA heuristics when choosing the next item in sequential focus order.
  • It seems correct in what it considers "never focusable", e.g. hidden elements, disabled elements.

I think the tabindex focus flag is especially confusing because sometimes it's set according to UA convention, sometimes it's set with a "must", and it always only has a "should" effect on sequential focus order. It's not clear what's up to the UA and what's required by the spec.

Also, the name "tabindex focus flag" is very uninformative.

Path forward

I think we should overhaul the spec's model to follow the above.

I think ideally we could have the invariant that something is only sequentially/click focusable if it is programmatically focusable. All browsers seem to follow this.

I think that, like the current spec, we can state that some things are never focusable, and continue to offer examples or guidance on what kind of things authors expect to be focusable, in what ways. But I don't think we can mandate anything besides that certain controls are never focusable.

The only thing I'm unsure about is whether "sequentially focusable" should be a property of a focusable area, or if there should be a "get next sequentially focusable area" algorithm. The latter seems like it's a bit more flexible; for example it feels weird to say that a control's sequential focusability changes on macOS depending on whether the Option key is held down. But having "sequentially focusable" as a first class concept seems much easier to understand, instead of inlining the logic into the "get next" algorithm.

Writing tests for this will not be very possible, given the amount of UA leeway to change the user experience here. We can test the programmatically focusable requirements, but not much else. And I suspect those are already tested reasonably well.

Exposing these to web components (WICG/webcomponents#762)

I think @rniwa is right, in WICG/webcomponents#762 (comment), that it is not going to work well to expose these primitives directly to web component authors. For example, an API that let you separately configure programmatically focusable, sequentially focusable, and click focusable, is not going to age well as we evolve user interaction paradigms, and will not make it easy for authors to integrate with the platform.

I think the spec should still explain the current landscape and implementation models, but in a way that still allows innovation, probably through liberal use of "may" and "should". I am confident we can do that, ending up with a result that is similar but less fictitious. And, as long as we don't expose an API that paints us into a corner, we can always fix the spec if we accidentally made it too strict.

I am currently leaning toward something that lets you state what built-in control you want to emulate, perhaps from a restricted list. We can continue that discussion in WICG/webcomponents#762.

tabIndex getter is mostly interoperable, but useless

This was the main subject of #4464; it was also touched on in #938 and #1786. The test page shows that most browsers give the same results for tabIndex. However, it is almost entirely uncorrelated with a control's focusability. So for developers, such as @muan in #4464 or many others I've heard from over the years, who want to tell whether a control is focusable, it is not helpful.

Path forward

I suggest we write down an interoperable spec for the tabIndex getter, based on what browsers do, and declare it useless for extracting focusability information.

We should then introduce a new getter, something like el.programmaticallyFocusable or el.canBeFocused, which gives whether a control is programmatically focusable. This does not expose any new information that could not be gotten today by creating the element, calling .focus(), and seeing if document.activeElement changed. This would help meet the desire of #4464, and e.g. would allow web developers to re-implement their own version of autofocus="" (extensible web manifesto).

I would like to introduce getters for whether a control is click focusable or sequentially focusable. However, I am unsure about baking in this kind of until-now-unobservable user interaction information, or how it would work with novel situations like macOS Safari's Option key business. I cannot see any way developers could usefully code against all browsers and still capture idiosyncracies like that:

// When the user opens a dialog with a keyboard,
// we want to focus the first sequentially-focusable control:
[...myDialog.children].find(el => el.sequentiallyFocusable).focus();

// Oh no. This code will do the wrong thing if the Option key is held down on macOS Safari.

So I think we'll have to leave that out for now

Other spec issues

Clarity: focusable areas

The spec's use of "focusable area", instead of working off of nodes, is confusing. There are three reasons why they are not 1:1:

  • The spec talks about focusing on viewports, and maps this to/from the Document itself having focus, and then maps the Document itself having focus to the <body> element being the activeElement. I am optimistic we could remove all this abstraction and just talk about the <body> element itself.
  • The spec says that area elements map to their img nodes. This doesn't appear to be implemented anywhere.
  • Sub-widgets of composite controls, which map to their parent control. (E.g. time pickers.) This is the only case that seems fundamentally important.

I'd like to simplifying this mapping significantly. I'm unsure whether we can completely get rid of the "focusable areas" concept given the sub-widgets issue, though. The best idea I have so far is to say that sub-widgets are handled by clauses like:

  • When sequentially navigating, sometimes the focused element won't change, because you'll be within a sub-widget.
  • Clicking on a widget might end up focusing only part of that widget in a way visible to the user.

Both of these are only at the user interaction level. From the web developer's point of view, sub-widgets are invisible.

Work to do: autofocus=""

Work to do: upstreaming shadow DOM work

#2013 tracks this. This section provides an overview of the desired behavior, and this section seems to attempt to implement most of it, but not in a way that's super well-integrated. (For example, it seems like the focus chain needs modification to account for shadow roots/delegatesFocus.)

@tkent-google and I will try to work on this. I don't yet understand how much this might overlap with the above.

Appendix: roughly-ordered task list

  • Fix autofocus existing foundations
  • Add autofocus="" to all elements
  • Fix overall types-of-focusability and tabindex focus flag model
  • Fix tabIndex getter definition (make it interoperable but not useful)
  • Add canBeFocused useful replacement for tabIndex getter
  • Upstream shadow DOM focus modifications
  • Simplify "focusable areas" concept in the spec
  • Add custom elements focus customization
  • Consider if we can/should expose click/sequential focusability to developers
@bzbarsky
Copy link
Contributor

@smaug----

@domenic
Copy link
Member Author

domenic commented May 10, 2019

One thing to follow up with: I am generally not proposing many, if any, changes to browser behavior. The majority of this issue is about aligning the spec with reality. The intent is to preserve browsers' flexibilities in terms of focusing behavior, while also documenting it accurately in the spec.

Concretely, I expect the only changes browser implementers will make will be:

  • Aligning on subtle edge cases around the tabIndex getter's return value
  • Aligning on subtle edge cases of autofocus="" behavior
  • Potentially adding a canBeFocused getter which does what tabIndex was supposed to do per spec, since this is a highly-requested web developer feature.
  • Implementing delegatesFocus for shadow DOM once we have a proper spec for it.

@annevk
Copy link
Member

annevk commented May 10, 2019

One aspect I do not see mentioned is focus rings, as discussed at #938 (comment).

@domenic
Copy link
Member Author

domenic commented May 10, 2019

Focus rings seem covered fairly well by the CSSWG. https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo

@bkardell
Copy link
Contributor

I wanted to add that I am hugely supportive of a big effort to as much as possible help explain/unwind focus and add something that developers can hope to understand and get right. I too have had so many conversations over the years on these topics. Another common frustration that I don't see specifcally logged here specifically (but might be covered by something you wrote?) is that we kind of overload the purpose of focus because it's all we have and this gets sort of confusing. Developers, for example, learning about keyboard accessibility initially tend to want to make all the things focusable, which is almost worse. So, we try to develop simple rules like "only interactive elements should be focusable" or "interactive elements shouldn't contain interactive elements", but these rules break down very quickly and wind up actively confusing people because, for example, many patterns require them. Skip links or in page anchors, for example, really want part of that - basically, they want to scroll/shift attention and where the search for the next sequentially focusable element (focus navigation starting point) begins, but not actually imply that this is an interactive element or something. Similarly, as you mentioned 'widgets' often manage a kind of manage what is effectively a 'secondary' focus. In some ways, shadow dom at least gives a concept here for that, but all ARIA patterns currently have to deal with all of that, traditionally in the light dom and I have both experienced and heard so much confusion around this. Managing where focus 'returns to' in all of these is frequently a very frustrating thing to try to manage as well.

@annevk
Copy link
Member

annevk commented May 10, 2019

@domenic how does "the user agent determines via heuristics that the focus should be made evident on the element" work for custom elements? At least reading the suggestions in that document it seems that might very much depend on particular details of the element.

@domenic
Copy link
Member Author

domenic commented May 10, 2019

Fair point. I guess that is further support for the "pick a native element to emulate" style of behavior.

@arei
Copy link

arei commented May 11, 2019

‪You may also want to look at the related FocusTraversalAPI proposal I submitted to the WICG a few months ago. I would love a whole lot more input on it and it desperately needs a major signal and visibility boost.

‪Explainer: https://github.com/awesomeeng/FocusTraversalAPI/blob/master/EXPLAINER.md

‪Proposal and discussion: https://discourse.wicg.io/t/proposal-focus-traversal-api/3427

@bkardell
Copy link
Contributor

There appear to be some encoded characters in the hrefs there causing them to not work but the displayed urls seem right

@arei
Copy link

arei commented May 11, 2019

@bkardell fixed the links. Thanks.

@muan
Copy link
Member

muan commented May 14, 2019

We should then introduce a new getter, something like el.programmaticallyFocusable or el.canBeFocused, which gives whether a control is programmatically focusable. This does not expose any new information that could not be gotten today by creating the element, calling .focus(), and seeing if document.activeElement changed. This would help meet the desire of #4464, and e.g. would allow web developers to re-implement their own version of autofocus="" (extensible web manifesto).

Seeing this I realized I had not considered the case of [tabindex="-1"][autofocus]. What #4464 needed is really sequentiallyFocusable. When something is [tabindex="-1"], programmaticallyFocusable would be true, but we wouldn't want to that to be focused when tabbing through elements in a dialog. This would still be a step-up from tabIndex however, but I imagine a condition like el.programmaticallyFocusable && el.tabIndex >= 0 will still be needed.

But of course this will still fail on option on macOS.

@domenic
Copy link
Member Author

domenic commented May 14, 2019

Yeah, the option on Safari-macOS issue, or any future innovations in focusablity, make exposing new things like sequential focusability a bit troublesome. (And in particular, putting myself in the Safari team's shoes, I'm not sure I'd want to add it to the platform.)

el.programmaticallyFocusable && el.tabIndex >= 0

In particular this only matches macOS Safari with option held down. I.e., this will return true for things like checkboxes or buttons which are not sequentially focusable there in the default state.

@arei
Copy link

arei commented May 15, 2019

@muan @domenic many of the common utilities around focus distinguish between focusable and tabbable, and ARIA plays into that as well. What I wrote in the FocusTraversal API (see previous posts) was to expose a means to determine what is focusable or not. It might serve to also expose the other characteristics around focus as well, eg what is tababble, what is autofocusable, etc.

@rniwa
Copy link

rniwa commented Jun 10, 2019

Is there a specific issue filed for area element? I think I agree with the statement that any element that's user focusable should also be programmatically focusable, and I'd consider area element's behavior in WebKit as a bug.

@domenic
Copy link
Member Author

domenic commented Jun 10, 2019

Nothing filed yet, as the spec isn't really clear on any of this. But I'll take the above as a sign that you'd appreciate an issue filed, even in the absence of a clear spec to support it :).

Bugs:

@domenic
Copy link
Member Author

domenic commented Jun 11, 2019

@tkent-google discovered that my test case for <area> was invalid. I was attempting to programatically focus an <area> with no href="", but I was clicking/tabbing through <area>s with href=""s.

I have closed the above two browser bugs as invalid, updated the test page to have "click me" programatically focus an <area> with a href="", and will now edit the OP to omit the incorrect information.

domenic pushed a commit that referenced this issue Jul 9, 2019
This updates the tabIndex getter's default value to be 0 for a specific
list of element types, instead of the spec's previous "elements that are
focusable". This matches reality better; apart from minor edge cases
all three browser engines match this spec in observable behavior.

Closes #1786. Closes #4464. See the focus meta-bug in #4607 for related
discussions, including on adding an API that actually reflects whether
an element is focusable (like the tabIndex getter was kind-of supposed
to do).

Tests: web-platform-tests/wpt#17657
@travisleithead
Copy link
Member

This is a great overview, thanks @domenic. Very complicated problem space to start tackling (and thus very rewarding, right, right?). This is an area that I know @melanierichards and @atanassov are also looking to participate in, so CC-ing them so they can start following too.

@domenic
Copy link
Member Author

domenic commented Aug 28, 2019

Dumping this here for reference. Here is what you can control about focusability of elements today:

  • Sequential focus order: via positive integer tabindex=""
  • Ability make something programmatically/click/non-sequentially focusable: via tabindex="-1"
    • But platform-aligned defaults can override, e.g. <button tabindex=-1> is not click-focusable on Safari.
  • Ability to make something programmatically/click/sequentially focusable: via tabindex="0".
    • But platform-aligned defaults can override again.

Contrast this with the "most primitive" model, which would give you something like:

  • Sequential focus ordering
  • Ability to make non-focusable
  • Ability to make programmatically/non-click/non-sequentially focusable
  • Ability to make programmatically/non-click/sequentially focusable
  • Ability to make programmatically/click/non-sequentially focusable
  • Ability to make programmatically/click/sequentially focusable

domenic pushed a commit that referenced this issue Sep 24, 2019
Define "focusable" more concretely, and as part of it, define special
types of focusability: click focusable and sequentially focusable.

As part of this, remove the "tabindex focus flag" because it can be
replaced with "focusable" and "focusable area", and was very confusing.

Part of #4607. Helps provide a basis for further work on #2013, but does
not directly contribute to any shadow DOM upstreaming.

This does not introduce any normative changes, but instead brings into
the spec behavior that was previously only in implementations, and makes
certain concepts explicit.
@zcorpan
Copy link
Member

zcorpan commented Oct 16, 2019

Some more stuff to consider about focus that I don't see mentioned above:

@zcorpan
Copy link
Member

zcorpan commented Oct 16, 2019

zcorpan pushed a commit that referenced this issue Nov 6, 2019
Define "focusable" more concretely, and as part of it, define special
types of focusability: click focusable and sequentially focusable.

As part of this, remove the "tabindex focus flag" because it can be
replaced with "focusable" and "focusable area", and was very confusing.

Part of #4607. Helps provide a basis for further work on #2013, but does
not directly contribute to any shadow DOM upstreaming.

This does not introduce any normative changes, but instead brings into
the spec behavior that was previously only in implementations, and makes
certain concepts explicit.
@anawhj
Copy link

anawhj commented Apr 2, 2020

Hello. I wonder the progress of 'Simplify focusable areas concept in the spec' as one of the action item here.

According to the current spec, the scrollable element is one of the focusable areas, but it works differently between browsers. If Chrome can enable KeyboardFocusableScrollers option by default, it follows the spec and other browser, but it hasn't yet due to some issues.

Please refer the following discussion and example on why Chrome hasn't made the scrollable element to be a focusable by default yet.

@domenic
Copy link
Member Author

domenic commented Apr 2, 2020

Hi @anawhj. The spec allows browsers to determine whether something is a focusable area or not, so it's up to Chrome whether they want to make scrollable elements focusable by default or no.

The "Simplify focusable areas concept in the spec" action item isn't really related to this question; it's more about a spec refactoring.

@anawhj
Copy link

anawhj commented Apr 2, 2020

Hi @domenic. Thank you for the clear explanation! I thought the focusable area described at the spec as a table form would be the normative requirement so that we could prevent any fragmentation between browsers but it seems not.
https://html.spec.whatwg.org/multipage/interaction.html#focusable-area

I was just confused that the spec says the examples in the table are non-normative but the 6.5.2 section itself doesn't describe 'this section is non-normative' as the 6.5.1 section does, though.

I just wanted to get your experienced opinion on the issue I've mentioned above. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

10 participants