Problem/Motivation
The narrow version of the main menu constrains tabbing when moving forwards in the tabbing order, but doesn't constrain it when moving backwards in the tabbing order.
A user may easily become lost if they want to close the menu, but the accidentally overshoot when tabbing backwards to the close-menu button.
Steps to reproduce
- Use a viewport narrow enough to force the mobile burger-menu style.
- Press TAB until the menu button has focus, and activate it. The narrow menu will appear.
- Keep pressing TAB, and observe the focus moving through the menu items. When focus is on the last item, the next TAB press will move focus to the close-menu button. Focus has moved to the start of the menu. This is fine.
- Here comes the bug: When pressing Shift-TAB to move backwards in the tabbing order, tabbing isn't constrained. When focus is on the close-menu button, the next Shift-TAB press moves focus to the site name, and thence to the skip-to-main-content-link, and browser controls. Eventually it will wrap around to the site footer.
Proposed resolution
Contrain tabbing in both directions: when using TAB to go forwards, and Shift-TAB to go backwards.
When focus is on the close-menu button, the next Shift-TAB press should place focus on the last item in the menu.
Remaining tasks
User interface changes
API changes
Data model changes
Release notes snippet
Comment | File | Size | Author |
---|---|---|---|
#34 | Screen Recording 2021-04-09 at 16.36.32.mov | 2.36 MB | Gauravvvv |
#32 | interdiff-30-32.txt | 725 bytes | mherchel |
#32 | 3191077-32.patch | 9.32 KB | mherchel |
#30 | interdiff-27.30.txt | 3.01 KB | mherchel |
#30 | 3191077-30.patch | 9.06 KB | mherchel |
Comments
Comment #2
larowlanI feel like there's an existing issue to remove the custom tab code and use core's tabbing manager, but I can't find it
Comment #3
andrewmacpherson CreditAttribution: andrewmacpherson as a volunteer commentedYou're thinking of #3190242: Nav should use core/drupal.tabbingmanager instead of custom focus manager. I'm not convinced that's a good idea, and it's certainly not a stable blocker or even urgent.
This one on the other hand is a must-fix, regardless of implementation details.
Comment #4
andrewmacpherson CreditAttribution: andrewmacpherson as a volunteer commentedtypo in summary
Comment #5
andrewmacpherson CreditAttribution: andrewmacpherson as a volunteer commentedInteresting points about the existing Olivero implementation.
Comment #6
mherchelThis should fix it!
Comment #7
andrewmacpherson CreditAttribution: andrewmacpherson as a volunteer commentedFeels nice. I manually tested in Firefox and various Blinken browsers, on Linux.
Haven't tested this yet in: any mobile browsers, Safari (mac), IE11, MSEdge.
Comment #8
andrewmacpherson CreditAttribution: andrewmacpherson as a volunteer commentedAside, some probable follow-ups....
This looks a bit fragile to me. How did you arrive at this list of CSS selectors?
It's missing a few elements which are normally tabbable, notably
<summary>
. Also others like<audio controls>
,<video controls>
.It will also match a lot of things which are not expected to be tabbable:
<button tabindex="-1">
,<input type="hidden">
,<input disabled>
, and many similar combinations.A problem with setting the first/last tabbable elements is they might not be stable during the lifetime of the page. An
<input>
might become enabled or disabled. Atabindex
attribute might change from 0 to -1.Terminology nitpicks: the
navigation.es6.js
uses the terms "focusable" a lot. We're interested in what's tabbable, and that's not the same.Comment #9
mherchelChanges from comment #8 are remedied.
The code now dynamically queries the navigation for tabbable elements when tab is pressed.
I also moved the generation of the array of tabbable elements into its own function. It checks for all of the queries that are suggested, and also filters out elements that have
visibility: hidden
anddisplay: none
In addition to the above, I changed the terminology to say "tabbable" instead of "focusable"
Comment #10
mherchelForgot a period in my code comment.
Tugboat Preview
https://3191077-focus-trap-my31ah3yurgrqluspurwcaoflvidxst2.tugboat.qa/
Comment #11
mherchelFixing the linting errors from the previous patch.
Comment #12
ranjith_kumar_k_u CreditAttribution: ranjith_kumar_k_u at Zyxware Technologies commentedThe last patch works as expected .
Before patch
After patch
Comment #13
andrewmacpherson CreditAttribution: andrewmacpherson as a volunteer commentedEverything has been addressed. The approach to identifying tabbable elements is what @mherchel and I discussed in an a11y review call.
I've manually tested the Tugboat preview of patch #10 with Firefox and Chrome. The later patches don't change any logic, just keep linters happy.
Great work.
Comment #14
alexpottIt would great to add test coverage of this bug fix.
I discussed this with @nod and @lauriii and we were wondering why we're not leveraging the tabbable library that's part of core. I think we could consider refactoring to use that here - or maybe as part of follow-up. I'm not sure what makes more sense. Either way - having a test to prove we don't regress seems very worthwhile.
Comment #15
mherchelThe issue for that is at #3190242: Nav should use core/drupal.tabbingmanager instead of custom focus manager
One reason is that it currently relies on jQuery and jQueryUI, but there's some accessibility reasons in there too from @andrewmacpherson in the comments.
Comment #16
alexpott@mherchel thanks for linking that issue - good to see that in hand. We need to add tests here.
FWIW this seems to be doing exactly what the tabbable library can do - see https://www.drupal.org/node/3186532
Comment #17
alexpottActually this can be entirely replaced by the tabbable library - which at least is tested JS code. We can leave focus trap part of the discussion to the other issue but there's no reason to have this code in Olivero.
Comment #18
mherchelFrom what I gather, #3113649: Remove drupal.tabbingmanager's jQueryUI dependency is not yet committed. The current tabbable library has jQueryUI dependency that we do not want to introduce.
I'll postpone this until that's committed.
Comment #19
mherchelComment #20
alexpott@mherchel ah you're right we've still not added the tabbable library... we could still work on adding JS tests here and if #3113649: Remove drupal.tabbingmanager's jQueryUI dependency gets delay we could file a follow-up instead.
Comment #21
mherchel#3113649: Remove drupal.tabbingmanager's jQueryUI dependency is now committed.
Re-roll of #11 is attached.
Comment #22
mherchelUpdating patch to use the new core
tabbable
library was super easy. Patch and interdiff attached.Still need tests.
Comment #23
brianperry#3205434: Add Nightwatch Test Coverage for Olivero will make it possible to write a Nightwatch test to cover this issue.
Comment #24
mherchelRe-roll of #22
Comment #25
mherchelNow that #3205434: Add Nightwatch Test Coverage for Olivero is in, we can add tests!
I'm including
Latest Tugboat preview: https://3191077-focus-trap-2-ojzhptztqdzoz9kfnxhzodnlcph0eo37.tugboat.qa/
Comment #26
mherchelNeeds reroll after #3173900: Refactor Olivero's JavaScript Drupal behaviors to use once() was committed. Working on it.
Comment #27
mherchelre-roll attached.
Updated tugboat: https://3191077-focus-trap-3-raoktoqiyjg8tbctjkbd9exrnnyfvqio.tugboat.qa/
Comment #28
proeungPatch #27 looks good from a code perspective. I tested the patch and everything is working as expected. Users are now able to tab forward and backward in the tabbing order (see attached video screenshot). Marking as RTBC!
Comment #29
lauriiiThe global tabbable should be only accessed in the IIFE that's wrapping this whole file.
What are these changes?
We probably don't need this anymore given that it's not referenced anywhere.
How is this related to this issue?
Comment #30
mherchelGood call! Done.
These changes are related to
Good catch. Done
I haven't worked on that in probably a month, but I'm guessing I saw a console error. I removed this from this patch.
Comment #31
mherchelregarding @lauriii's question "What are these changes?" in #29... he was asking why we're attaching the event listener to the
header
... and the answer is that we really don't need to. I'm going to create a new patch.Comment #32
mherchelOK. Found out why I'm passing in the header prop. It's because the navigation button is not a child element of the navigation wrapper, which means that the keydown event would not fire.
I'm attaching a new patch with a more verbose comment.
Comment #33
proeungThe revised patch #32 looks good and addresses the feedback that @lauriii mentioned in #29 with the exception of line item (3), which @mherchel noted (#32) as something that we need and should not be removed.
RTBC +1
Comment #34
Gauravvvv CreditAttribution: Gauravvvv at OpenSense Labs commentedPatch #32 seems fine to me. I am able to tab forward and backward in the tabbing order.
RTBC +1
Comment #35
Gauravvvv CreditAttribution: Gauravvvv at OpenSense Labs commentedComment #37
lauriiiCommitted f3674e9 and pushed to 9.2.x. Thanks!