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

[Popup] focus anchor after hide? #327

Closed
melanierichards opened this issue Apr 12, 2021 · 11 comments
Closed

[Popup] focus anchor after hide? #327

melanierichards opened this issue Apr 12, 2021 · 11 comments
Labels
popover The Popover API

Comments

@melanierichards
Copy link
Collaborator

On MicrosoftEdge/MSEdgeExplainers#441, @carmacleod mentioned:

I didn't see any mention in the explainer of where focus goes after the popup is hidden (i.e. in the case where focus was moved into the popup and didn't just stay on the anchor).

Maybe it's just left up to the author, but if the author doesn't manage focus on hide (which happens), then I think there's an opportunity for a nice default behavior, which is to return focus to the anchor, if there is one. It would be good if the "focus after hide" behavior was explicit in the explainer (perhaps in the Dismissing the popup section).

This seems like a good behavior from a usability/accessibility perspective, assuming that the popup didn't dismiss because the user moved focus out of the popup explicitly.

@melanierichards
Copy link
Collaborator Author

One small nit here to my original issue, we should specify that focus returns to the previously-focused element (which in many cases will be the popup's anchor), unless the popup was hidden because the user expressly moved focus.

This would align with a recent change to <dialog> behavior. Refer to whatwg/html#5678 and whatwg/html@5d50bfa

@melanierichards melanierichards added the agenda+ Use this label if you'd like the topic to be added to the meeting agenda label Apr 28, 2021
@gregwhitworth gregwhitworth removed the agenda+ Use this label if you'd like the topic to be added to the meeting agenda label May 5, 2021
@github-actions
Copy link

There hasn't been any discussion on this issue for a while, so we're marking it as stale. If you choose to kick off the discussion again, we'll remove the 'stale' label.

@mfreed7
Copy link
Collaborator

mfreed7 commented May 6, 2022

Bringing this issue back up, as I've started to think about implementing this behavior for Popup.

There are some differences between popup and dialog, and I'm not entirely sure what the right behavior is:

  • The change (AFAIK) to <dialog> was only for modal dialogs, where the focus is trapped. That is different from popups which do not focus-trap.
  • One of the light dismiss triggers for popup is moving the focus out of the popup. It would seem very odd for the behavior in that case to be that the focus suddenly shifts back to another element when the popup is light dismissed via tabbing out of it.
  • Note this comment Move the focus back to the previous focused element for dialog.close() whatwg/html#5678 (comment) which seems to indicate the right behavior for popups is to move back to the triggering element. That isn't a thing for <dialog>, but very definitely is for popup. So we could go with that? Even that is odd, if the popup is light dismissed via tabbing out.
  • Popup isn't focused/focusable by default, so what if focus never leaves another element. I suppose this is a no-op anyway. But it is a difference vs. <dialog>.

Suggestions/thoughts/input appreciated. Particularly from the a11y community.

@scottaohara
Copy link
Collaborator

scottaohara commented May 6, 2022

where focus goes in many cases will probably be similar across various types of popup=whatever-default-is, but the tab key dismissal is a bit of where the type of popup becomes a factor.

For the general bits:

  • if a popup is dismissed via the Esc key, then focus returns to the element which invoked the popup.
  • if a popup is dismissed due to the selection/action being made within the popup, or if a user activates a 'dismiss/close' button within the popup, AND a developer had not specified another element for focus to return to [note], focus returns to the element which invoked the popop.
    Note: this might be necessary if, for instance, performing a delete action and the invoking element is no longer in the DOM, thus a different element would need to be specified to be focused.
  • if a popup is dismissed by pointer event outside of the popup, focus does not return to the invoking popup, but does essentially go to whatever element which accepts focus was clicked, or if some arbitrary non-focusable content in the document was clicked - a browser might try to make sure that, if the tab key was pressed, keyboard focus would go to the appropriate focusable element in relation to where that click occurred.

now, depending on the type of popup, and under the assumption a popup has not been made to trap focus - as one may be want to do - pressing the tab key would return focus to either:

  • the next focusable element within the document's tab order
  • OR, the element which invoked the popup

For instance, the current <select> element returns focus to the invoking trigger if tab key is pressed while the listbox popup is open. (note: a listbox popup from a type-ahead text field - e.g., <input list=f><datalist id=f>...</datalist> - does NOT return focus to the text field, but DOES commit the selection.)

On the flip side, many custom components (e.g., menu, and their submenus) do allow for tab key to leave them, focus the next element in the DOM order, and the previous menu popup is dismissed).

With all that said, and particularly in regards to the tab key, my opinion is that a general author defined popup should default to moving focus to the next appropriate element in the DOM order and thus light dismissing the popup. If someone is building their own thing, then you can't reasonably know what it is they've built, or even if they are trying to adhere to common conventions. Applying ARIA attributes does not change the implicit functionality of the element of which the attributes have been set, so even if someone was creating a custom listbox popup - they'd be on the hook for doing it right.

Rather, a platform defined element which invokes a popup (e.g., the hopefully eventual <selectmenu>'s popup) - i think it would be reasonable to expect that in this instance pressing the tab key would return focus to the invoking element to maintain some parity with the current <select> element's behavior.

@github-actions github-actions bot removed the stale label May 7, 2022
@mfreed7
Copy link
Collaborator

mfreed7 commented May 10, 2022

@scottaohara thanks for the detailed response.

For instance, the current <select> element returns focus to the invoking trigger if tab key is pressed while the listbox popup is open. (note: a listbox popup from a type-ahead text field - e.g., <input list=f><datalist id=f>...</datalist> - does NOT return focus to the text field, but DOES commit the selection.)

Quick point: this isn't true on a Mac with native <select> menu popups. Hitting tab there does nothing at all.

With all that said, and particularly in regards to the tab key, my opinion is that a general author defined popup should default to moving focus to the next appropriate element in the DOM order and thus light dismissing the popup. If someone is building their own thing, then you can't reasonably know what it is they've built, or even if they are trying to adhere to common conventions. Applying ARIA attributes does not change the implicit functionality of the element of which the attributes have been set, so even if someone was creating a custom listbox popup - they'd be on the hook for doing it right.

Ok, great, so it sounds like you think (in addition to your other points) that hitting Tab should move focus to the next focusable element in the DOM.

Rather, a platform defined element which invokes a popup (e.g., the hopefully eventual <selectmenu>'s popup) - i think it would be reasonable to expect that in this instance pressing the tab key would return focus to the invoking element to maintain some parity with the current <select> element's behavior.

I'll defer to your judgement for <selectmenu> but the important point is that this behavior is up to the component developer. In the general case, we should move to the next focusable element.

So to summarize what you think should happen (correct me if I missed something):

  1. Close signal (e.g. ESC): return to previously focused element.
  2. Hide popup via hidePopup(): return to previously focused element.
  3. Hide popup via a popup-contained element with hidepopup=popup_id: return to previously focused element.
  4. Click outside the popup: change focus to whatever was clicked (or ordinary behavior if that thing isn't focusable).
  5. Tab-navigate: change focus to whatever would be next (or previous for shift-Tab) in the document.

In all cases, "return to previously focused element" should behave as it does for <dialog>.

One last note about "last focused element" or "element that invoked the popup" - they should be the same. I.e. when an invoking element is activated to show a popup, it'll be the last focused element.

@scottaohara
Copy link
Collaborator

scottaohara commented May 11, 2022

Quick point: this isn't true on a Mac with native <select> menu popups. Hitting tab there does nothing at all.

That's true for chromium/webkit. Firefox behaves as I mentioned above / how the browsers behave on PC. 'think different' ;)

Ok, great, so it sounds like you think (in addition to your other points) that hitting Tab should move focus to the next focusable element in the DOM. ....In the general case, we should move to the next focusable element.

yes. that seems the safest default that authors could override if they so choose. And yes, for a particular component from the platform, they'd do what they will with tab key dismissal (including not allowing it at all, apparently :) )

@mfreed7
Copy link
Collaborator

mfreed7 commented May 11, 2022

@scottaohara awesome, thanks. I'm going to agenda+ this to hopefully get that resolved.

@mfreed7 mfreed7 added the agenda+ Use this label if you'd like the topic to be added to the meeting agenda label May 11, 2022
@mfreed7
Copy link
Collaborator

mfreed7 commented May 17, 2022

Clarifying and adding a few things as I implement this:

These actions cause focus to be returned to the previously-focused element:

  1. Close signal (e.g. ESC).
  2. Hide popup via hidePopup().
  3. Hide popup via a popup-contained triggering element with hidepopup=popup_id or togglepopup=popup=id.
  4. Hide popup because its popup type changes (e.g. via mypopup.popup='something else';).

These actions do not cause focus to be returned to the previously-focused element. Instead, the "normal" behavior happens for each action:

  1. Click outside the popup (focus the clicked thing).
  2. Click on a non-popup-contained triggering element to close popup (focus the triggering element). Special case of the point just above.
  3. Tab-navigate (focus the tab-focused thing).
  4. Hide popup because it was removed from the document (event handlers not allowed while removing elements).
  5. Hide popup when a modal dialog or fullscreen element is shown (follow dialog/fullscreen focusing steps).
  6. Hide popup via showPopup() on another popup that hides this one (follow new popup focusing steps).

@mfreed7
Copy link
Collaborator

mfreed7 commented May 17, 2022

Here's another funny corner case:

  1. Focus an unrelated element foo on the page.
  2. Have a popup1 which does not contain any focusable elements.
  3. Call popup1.showPopup(). At this point, popup1 will remember that foo was the previously_focused_element.
  4. Have a popup2 which is "nested" in popup1.
  5. Call popup2.showPopup(). At this point, since popup1 didn't change the focus, popup2 will also remember that foo was its previously_focused_element.
  6. Call popup2.hidePopup(). This is the problem. Here, popup2 will attempt to restore focus to foo, which is outside the ancestor chain of popup1. So that will (per the resolution) light-dismiss popup1 also.

I'm thinking the appropriate tweak would be to only set previously_focused_element when showing a popup if the popup stack is currently empty. That would mean only popup1 in our example above would try to restore focus when it closes, and popup2 wouldn't change focus.

Another tweak to the above rules: I don't think focus should be returned/changed when hiding async popups. They don't take focus initially, and they're asynchronous, so they shouldn't ever change focus.

chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this issue May 18, 2022
Akin to <dialog>'s behavior, the desired behavior is that focus
is returned to the previously-focused element when a popup is
hidden:

  openui/open-ui#327

Bug: 1307772
Change-Id: Ia6ae1981612a0c0150b8b5f51b485a00a9a90de9
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this issue May 18, 2022
Akin to <dialog>'s behavior, the desired behavior is that focus
is returned to the previously-focused element when a popup is
hidden:

  openui/open-ui#327

Bug: 1307772
Change-Id: Ia6ae1981612a0c0150b8b5f51b485a00a9a90de9
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this issue May 19, 2022
Akin to <dialog>'s behavior, the desired behavior is that focus
is returned to the previously-focused element when a popup is
hidden:

  openui/open-ui#327

Bug: 1307772
Change-Id: Ia6ae1981612a0c0150b8b5f51b485a00a9a90de9
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this issue May 20, 2022
Akin to <dialog>'s behavior, the desired behavior is that focus
is returned to the previously-focused element when a popup is
hidden:

  openui/open-ui#327

Bug: 1307772
Change-Id: Ia6ae1981612a0c0150b8b5f51b485a00a9a90de9
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this issue May 20, 2022
Akin to <dialog>'s behavior, the desired behavior is that focus
is returned to the previously-focused element when a popup is
hidden:

  openui/open-ui#327

Bug: 1307772
Change-Id: Ia6ae1981612a0c0150b8b5f51b485a00a9a90de9
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this issue May 21, 2022
Akin to <dialog>'s behavior, the desired behavior is that focus
is returned to the previously-focused element when a popup is
hidden:

  openui/open-ui#327

Bug: 1307772
Change-Id: Ia6ae1981612a0c0150b8b5f51b485a00a9a90de9
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this issue May 21, 2022
Akin to <dialog>'s behavior, the desired behavior is that focus
is returned to the previously-focused element when a popup is
hidden:

  openui/open-ui#327

Bug: 1307772
Change-Id: Ia6ae1981612a0c0150b8b5f51b485a00a9a90de9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3627159
Commit-Queue: Mason Freed <[email protected]>
Auto-Submit: Mason Freed <[email protected]>
Reviewed-by: David Baron <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1006180}
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this issue May 21, 2022
Akin to <dialog>'s behavior, the desired behavior is that focus
is returned to the previously-focused element when a popup is
hidden:

  openui/open-ui#327

Bug: 1307772
Change-Id: Ia6ae1981612a0c0150b8b5f51b485a00a9a90de9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3627159
Commit-Queue: Mason Freed <[email protected]>
Auto-Submit: Mason Freed <[email protected]>
Reviewed-by: David Baron <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1006180}
moz-v2v-gh pushed a commit to mozilla/gecko-dev that referenced this issue May 24, 2022
…nt on popup hide, a=testonly

Automatic update from web-platform-tests
Return focus to previously-focused element on popup hide

Akin to <dialog>'s behavior, the desired behavior is that focus
is returned to the previously-focused element when a popup is
hidden:

  openui/open-ui#327

Bug: 1307772
Change-Id: Ia6ae1981612a0c0150b8b5f51b485a00a9a90de9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3627159
Commit-Queue: Mason Freed <[email protected]>
Auto-Submit: Mason Freed <[email protected]>
Reviewed-by: David Baron <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1006180}

--

wpt-commits: f4a6afa267e1d7aed8191b85c989c2eb54ad1b12
wpt-pr: 34116
jamienicol pushed a commit to jamienicol/gecko that referenced this issue May 25, 2022
…nt on popup hide, a=testonly

Automatic update from web-platform-tests
Return focus to previously-focused element on popup hide

Akin to <dialog>'s behavior, the desired behavior is that focus
is returned to the previously-focused element when a popup is
hidden:

  openui/open-ui#327

Bug: 1307772
Change-Id: Ia6ae1981612a0c0150b8b5f51b485a00a9a90de9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3627159
Commit-Queue: Mason Freed <[email protected]>
Auto-Submit: Mason Freed <[email protected]>
Reviewed-by: David Baron <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1006180}

--

wpt-commits: f4a6afa267e1d7aed8191b85c989c2eb54ad1b12
wpt-pr: 34116
@css-meeting-bot
Copy link

The Open UI Community Group just discussed [Popup] focus anchor after hide?, and agreed to the following:

  • RESOLVED: focus the previously focused element when it makes sense. Handle the corner cases carefully, and define the behavior more precisely.
The full IRC log of that discussion <gregwhitworth> Topic: [Popup] focus anchor after hide?
<gregwhitworth> github: https://github.com//issues/327
<gregwhitworth> masonf: focusing the previously focused element
<gregwhitworth> masonf: in dialog, when you focus the dialog and when you close it or hide it focus moves to where you used to be
<gregwhitworth> masonf: there seems to be a desire to do the same thing, although tthere are complications
<gregwhitworth> masonf: for example when you light dismiss and click another button, but you wouldn't want to focus elsewhere when you move to another focusable element
<gregwhitworth> masonf: you shouldn't jump to something else
<gregwhitworth> masonf: there's a more complex scenarios when you have a stacked popup
<gregwhitworth> masonf: you open a nested popup you don't want to pop back to the first thing because it would close the first popup
<gregwhitworth> masonf: let's resolve to move to the prior focused element where it makes sense
<JonathanNeal> +1 to returning focus to the popup (anchor?) when it “makes sense”.
<masonf> Proposed resolution: focus the previously focused element when it makes sense. Handle the corner cases carefully, and define the behavior more precisely.
<gregwhitworth> +1 with the caveat that the proposed resolution to define "where it makes sense"
<dandclark> q+
<gregwhitworth> ack una
<gregwhitworth> una: I would +1 as well and saw scotto_ +1 it in the issue
<gregwhitworth> ack dandclark
<gregwhitworth> dandclark: I agree with the stated resolution
<gregwhitworth> dandclark: I'm a little worried about the complexity getting out of control
<gregwhitworth> dandclark: I would think that each stacked popup would need its own idea of what is focused before me
<gregwhitworth> dandclark: I guess I want to leave room to backing off if this is really complicated
<JonathanNeal> +1 to the proposed resolution
<gregwhitworth> masonf: I just want to say that I've already implemented this in Chromium
<gregwhitworth> masonf: the thing I mentioned in the scenario for nested popups
<gregwhitworth> dandclark: that makes me feel a lot better
<masonf> q?
<masonf> RESOLVED: focus the previously focused element when it makes sense. Handle the corner cases carefully, and define the behavior more precisely.
<bkardell_> +1

@mfreed7
Copy link
Collaborator

mfreed7 commented May 26, 2022

Ok, per the resolution, I've written up what I believe to be the correct behavior in the explainer. See the PR here. It is essentially the behavior discussed in this comment plus this comment. And it is what I've already implemented in the Chromium prototype.

I'm going to close this issue, but if anyone finds issues with the explainer description or the Chromium prototype behavior, please do file an issue here (or on https://crbug.com/new) and I'll take a look!

@mfreed7 mfreed7 closed this as completed May 26, 2022
@mfreed7 mfreed7 removed the agenda+ Use this label if you'd like the topic to be added to the meeting agenda label May 26, 2022
mjfroman pushed a commit to mjfroman/moz-libwebrtc-third-party that referenced this issue Oct 14, 2022
Akin to <dialog>'s behavior, the desired behavior is that focus
is returned to the previously-focused element when a popup is
hidden:

  openui/open-ui#327

Bug: 1307772
Change-Id: Ia6ae1981612a0c0150b8b5f51b485a00a9a90de9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3627159
Commit-Queue: Mason Freed <[email protected]>
Auto-Submit: Mason Freed <[email protected]>
Reviewed-by: David Baron <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1006180}
NOKEYCHECK=True
GitOrigin-RevId: f1e305a9f30be10bebbe48a83aa104bbcd85db7f
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
popover The Popover API
Projects
None yet
Development

No branches or pull requests

5 participants