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

[Menubar] Adds keyboard behaviors to the menubar, improves accessibility #3320

Open
wants to merge 84 commits into
base: develop
Choose a base branch
from

Conversation

tespin
Copy link
Contributor

@tespin tespin commented Jan 17, 2025

Fixes #2933

Just setting this up to track next steps for the component! This PR focuses on adding proper keyboard behaviors to the recently refactored Menubar. Currently in progress


Update

The Menubar component now has keyboard navigation and enhanced accessibility support. There are some other improvements to make which are listed at the end, but I'll implement them in future PRs seeing as this one has gotten rather large.

Before:
Navigation happened purely through Tab / Shift + Tab. Escape functionality worked but otherwise behaviors were not intuitive or accessible. Focus is not tied to state.

old_menubar_2.mov

After:
Most keyboard behaviors from the WAI ARIA authoring guide implemented. Tab behavior works as expected and initial focus styles are available.

menubar_test.mov

Key Changes

  • Implemented roving tab index in Menubar.jsx for focus management
  • Updated aria roles, such as aria-orientation='horizontal'on Menubar and aria-labels where applicable
  • Navigation support, including:
    • Horizontal and vertical navigation
    • Home/end, enter/space support
    • Basic focus trapping in submenus
  • Improved HTML structure
    • wrapped Menubar in nav element
    • moved from div-based menubar to ul/li
    • Logo is moved outside of the nav, UserMenu is back in
    • left and right menu items now wrapped in ul elements with role=group
  • added SubmenuContext for managing submenu state
  • MenubarSubmenu supports listbox aria roles, such as with the options in LanguageMenu
  • basic tests checking for checking component renders, keyboard behavior, and aria roles
  • added keyboard styles
  • added basic JSDoc comments for components

To Do

  • testing:
    • test with screen readers
    • verify aria structure and role handling
  • errors:
    • focus is not properly entering certain modals
    • some timing issues with effects and blur/focus handlers
  • optimizations:
    • consider refactoring context structure
    • memoize index and menuItem calculations
    • menuItems and menuItemToId could probably be combined into a better data structure
    • consolidate the more complex state updates with useReducer
  • other features/improvements:
    • add support for matching focused menu item to pressed character key
    • complete focus styles
    • extract common logic into hooks
    • tests could be more thorough
    • implement better compound component pattern

I have verified that this pull request:

  • has no linting errors (npm run lint)
  • has no test errors (npm run test)
  • is from a uniquely-named feature branch and is up to date with the develop branch.
  • is descriptively named and links to an issue number, i.e. Fixes #123

Sorry, something went wrong.

Unverified

This user has not yet uploaded their public signing key.

Unverified

This user has not yet uploaded their public signing key.

Unverified

This user has not yet uploaded their public signing key.

Unverified

This user has not yet uploaded their public signing key.

Unverified

This user has not yet uploaded their public signing key.

Unverified

This user has not yet uploaded their public signing key.

Unverified

This user has not yet uploaded their public signing key.
@tespin tespin mentioned this pull request Jan 17, 2025
10 tasks

Unverified

This user has not yet uploaded their public signing key.

Unverified

This user has not yet uploaded their public signing key.

Unverified

This user has not yet uploaded their public signing key.

Unverified

This user has not yet uploaded their public signing key.

Unverified

This user has not yet uploaded their public signing key.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Extract logo to header level for better semantic structure
  • Ensure arrow keys only cycle through menu items, excluding the logo

tespin added 15 commits January 18, 2025 14:50

Unverified

This user has not yet uploaded their public signing key.

Unverified

This user has not yet uploaded their public signing key.

Unverified

This user has not yet uploaded their public signing key.

Unverified

This user has not yet uploaded their public signing key.

Partially verified

This commit is signed with the committer’s verified signature.
goestav’s contribution has been verified via GPG key.
We cannot verify signatures from co-authors, and some of the co-authors attributed to this commit require their commits to be signed.
… array to set
@tespin tespin marked this pull request as ready for review February 1, 2025 09:32
import usePrevious from '../../common/usePrevious';

/**
* @component
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love the addition of documentation for the components!

Copy link
Collaborator

@raclim raclim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks so much for continuing the work on the navigation menu section—I really love the transformation you've been giving it!

I think overall this PR looks great so far! I feel like there's also a few great practices that are being introduced here (such as the formatting of the PR, component documentation, more detailed commenting) that could be adopted throughout the codebase as well! I also agree that this is a good point to stop at for this PR, and the rest of the tasks outlined in the description could be addressed in future ones.

I think the only issue I might've found is that I noticed that when using the arrow keys to navigate specifically the File SubMenu, it ends up skipping over "Open".

nav_file_menu.mov

Besides this, I mostly had a few questions while testing it out and looking through the code, which I'll list out below!

  1. When navigating the SubMenus, I noticed that both the mouse hover and arrow key inputs are visible, which I tried to depict in the video. I feel like this could get a bit confusing, but unsure if it might've been intentional!
nav_language_menu.mov
  1. I also noticed that the focus state immediately appears on the Menubar as soon as I landed on the editor page. I was wondering if this might've been intended as well!
Screenshot 2025-02-17 at 10 08 18 PM
  1. I know there's a few different opinions around adding comments, I was curious about why you decided to add more explanatory comments throughout the components!

  2. While looking through MenubarSubmenu.jsx, I feel like it could be broken down into maybe a few files, but was wondering if you felt that way as well!

@tespin
Copy link
Contributor Author

tespin commented Feb 27, 2025

I think the only issue I might've found is that I noticed that when using the arrow keys to navigate specifically the File SubMenu, it ends up skipping over "Open".

ooo interesting ... for some reason this didn't happen to me earlier but now it's occurring. it also gets very weird when I save the sketch and new menu items appear? seems like it has something to do with hideIf prop. the way I'm tracking the menu items probably isn't accounting for the conditionally rendered options. definitely needs another look.

separately, i also run into an error when the user is unauthenticated and attemps to click "Login" or "Signup", as it's looking for a menuitem element to focus but there isn't one in this case.

1. When navigating the SubMenus, I noticed that both the mouse hover and arrow key inputs are visible, which I tried to depict in the video. I feel like this could get a bit confusing, but unsure if it might've been intentional!

definitely not intentional, should probably prioritize one interaction over the other!

2. I also noticed that the focus state immediately appears on the Menubar as soon as I landed on the editor page. I was wondering if this might've been intended as well!

i should fix this as well, the auto-focusing feels disruptive and likely isn't wcag-oriented.

3. I know there's a few different opinions around adding comments, I was curious about why you decided to add more explanatory comments throughout the components!

thanks for this question! most comments I added for myself just to keep track of what was happening as it was getting a little complex ... i also felt that it wasn't always clear what some code was doing. i can probably remove some of the comments for code that is more self-evident.

4. While looking through `MenubarSubmenu.jsx`, I feel like it could be broken down into maybe a few files, but was wondering if you felt that way as well!

yup, agreed! this is a change that makes sense to me.

i know it's a lot to review so thanks for taking the time! let me go back in and address some of the more urgent bugs regarding the auto-focusing and weird navigation. as for the other issues (mouse and key inputs being visible, breaking down the submenu into smaller files, etc ...) do you think it's ok if i add those to this PR or can I address them in future ones?

@raclim
Copy link
Collaborator

raclim commented Mar 4, 2025

i know it's a lot to review so thanks for taking the time! let me go back in and address some of the more urgent bugs regarding the auto-focusing and weird navigation. as for the other issues (mouse and key inputs being visible, breaking down the submenu into smaller files, etc ...) do you think it's ok if i add those to this PR or can I address them in future ones?

That sounds good to me!! I think the other issues can probably be addressed in future ones, but please feel free to go ahead with whichever feels best to you! Thanks so much again for your efforts on this! :)

@raclim raclim added the Area:Accessibility Category for accessibility related features and bugs label Mar 6, 2025
@tespin tespin mentioned this pull request Mar 17, 2025
4 tasks
@tespin
Copy link
Contributor Author

tespin commented Mar 22, 2025

update: part of the navigation bug looks related to the early return in MenubarItem where we return null if hideIf is true. i moved the registration logic above that and added a check to only register if the menuitem is visible.

this seems to work but breaks again if the user refreshes the page. might need to dig a little deeper into this ...

@dipamsen
Copy link
Contributor

dipamsen commented Mar 22, 2025

When navigating the SubMenus, I noticed that both the mouse hover and arrow key inputs are visible, which I tried to depict in the video. I feel like this could get a bit confusing, but unsure if it might've been intentional!

Here's one way it could work: https://www.w3.org/WAI/ARIA/apg/patterns/menubar/examples/menubar-editor/

It only ever highlights a single item at a time, which follows the keyboard navigation, but jumps to a different element on mouse hover. Maybe this is not very easy to implement and requires additional logic.

Another option could be to have different styles on hover and focus, since they have different semantics (eg. pressing enter will select the focused option, not the hovered one)

@tespin
Copy link
Contributor Author

tespin commented Mar 23, 2025

@dipamsen thanks for linking the example! that's been the model for this menubar's implementation and is aligned with other examples i've seen -- i'll be using the same approach.

@raclim spent a little more time with the navigation issue. i think the problem is in the menuitems which get conditionally rendered; there seems to be a conflict between when the menuitems are registered and when the conditions for hideIf change. hideIf tends to change after the component mounts, so when the submenu item eventually registers, it gets added to the end of the submenuItems set. even if they are rendered a different way they still get navigated to as if they were the last items. e.x. if a user signs in, saves the sketch, then refreshes the page, the items which depend on isUnsaved (Duplicate, Share, etc) are added at the end of the navigation order.

i'm curious how you'd feel if, instead of conditionally rendering the items, maybe keeping them visible but disabled instead? that way the ordering of the menuitems can be stable regardless of visibility. i know that the example menubars we looked at above do something similar as well, depending on the context, so it looks like an established pattern. it will probably be an adjustment though, so i just wanted to check in, in case there was an argument for keeping the conditional rendering.

@tespin
Copy link
Contributor Author

tespin commented Mar 26, 2025

here's how things look with the disabled but visible approach.

unsaved, unauthenticated
Screenshot 2025-03-26 at 12 14 00 PM

unsaved, authenticated
Screenshot 2025-03-26 at 12 14 48 PM

saved, authenticated
Screenshot 2025-03-26 at 12 15 06 PM

i used some existing inactive styles. if we go this route we should probably push it more since it's still hard to tell if an item is disabled or not. navigation works as expected too. we can add tooltips or modals to clarify why an item is disabled.

@raclim
Copy link
Collaborator

raclim commented Mar 27, 2025

spent a little more time with the navigation issue. i think the problem is in the menuitems which get conditionally rendered; there seems to be a conflict between when the menuitems are registered and when the conditions for hideIf change. hideIf tends to change after the component mounts, so when the submenu item eventually registers, it gets added to the end of the submenuItems set. even if they are rendered a different way they still get navigated to as if they were the last items. e.x. if a user signs in, saves the sketch, then refreshes the page, the items which depend on isUnsaved (Duplicate, Share, etc) are added at the end of the navigation order.

Thanks so much for catching this—that explains a lot! It makes so much sense when I interact with the menubar now!!! 😂

i'm curious how you'd feel if, instead of conditionally rendering the items, maybe keeping them visible but disabled instead? that way the ordering of the menuitems can be stable regardless of visibility. i know that the example menubars we looked at above do something similar as well, depending on the context, so it looks like an established pattern. it will probably be an adjustment though, so i just wanted to check in, in case there was an argument for keeping the conditional rendering.

Hmm, yeah since disabling the menuitems seems like an established pattern, I'm totally on board with going that route here! I think the conditional rendering was originally used because the component was first built using toggleDropdown to control when the menu items would render.

i used some existing inactive styles. if we go this route we should probably push it more since it's still hard to tell if an item is disabled or not. navigation works as expected too. we can add tooltips or modals to clarify why an item is disabled.

Thanks so much for linking the screenshots and confirming that it seems to work! I'm wondering do you think it makes sense to break this out into a separate PR, or is it better to keep it within this one?

@tespin
Copy link
Contributor Author

tespin commented Mar 27, 2025

Thanks so much for linking the screenshots and confirming that it seems to work! I'm wondering do you think it makes sense to break this out into a separate PR, or is it better to keep it within this one?

i thought i'd want to break it out, but i think it makes more sense to keep in the current PR since it feels necessary for the component to work. i'll keep the changes small we can open up another PR in the future in case we need to make improvements.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area:Accessibility Category for accessibility related features and bugs
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Up/Down arrow keys not working on dropdown
3 participants