-
Notifications
You must be signed in to change notification settings - Fork 136
[UEPR-445] Ensure topbar is navigable via tab navigation #403
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
base: develop
Are you sure you want to change the base?
[UEPR-445] Ensure topbar is navigable via tab navigation #403
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements keyboard navigation and accessibility improvements for the top navigation bar, enabling users to navigate menu items using Tab and arrow keys. The changes introduce a new context-based system for tracking open menus and managing focus states across nested menu hierarchies.
Key Changes:
- Added
tabIndexattributes and ARIA properties to make menu bar elements keyboard-navigable - Implemented arrow key navigation within dropdown menus (Settings, Language, Theme)
- Created
MenuRefContextto manage the state of open menus and focus tracking across the menu hierarchy
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/scratch-gui/src/components/context-menu/menu-path-context.jsx | New context provider for tracking open menu references and navigation state |
| packages/scratch-gui/src/components/menu-bar/settings-menu.jsx | Converted to class component with keyboard navigation handlers |
| packages/scratch-gui/src/components/menu-bar/language-menu.jsx | Added keyboard navigation with arrow keys and Enter/Escape handlers |
| packages/scratch-gui/src/components/menu-bar/theme-menu.jsx | Added keyboard navigation similar to language menu |
| packages/scratch-gui/src/components/menu/menu.jsx | Added accessibility props (focusedRef, aria attributes, keyboard handlers) |
| packages/scratch-gui/src/components/menu-bar/menu-bar.jsx | Added tabIndex and ARIA attributes to menu bar items for keyboard access |
| packages/scratch-gui/src/components/gui/gui.jsx | Wrapped MenuBar with MenuRefProvider context |
| packages/scratch-gui/src/containers/gui.jsx | Contains commented test code |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
packages/scratch-gui/src/components/context-menu/menu-path-context.jsx
Outdated
Show resolved
Hide resolved
…ex to focusedItem
…u-specific css file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 32 out of 33 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| tabIndex={-1} | ||
| aria-label={ariaLabel} | ||
| aria-selected={isSelected} | ||
| aria-disabled={isDisabled} |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The aria-disabled attribute should be a string ('true' or 'false'), not a boolean value. Change to aria-disabled={isDisabled ? 'true' : 'false'} or use the disabled attribute directly on interactive elements.
| aria-disabled={isDisabled} | |
| aria-disabled={isDisabled ? 'true' : 'false'} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure this is right? I think it converts to string by itself
adzhindzhi
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left a few minor comments, but it looks great overall!
| data-menu-item | ||
| data-menu-item-wrapper |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you remind me why is it both a menu-item and a wrapper?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My bad. Top level menus don't need to be either of those. Must have been from some former logic. It literally does not make a difference. I might have to consider keeping the menu-item in case they stop being top level menus?
811a72d to
5c6d9ff
Compare
…ck about menu icon style
5c6d9ff to
d393d77
Compare
adzhindzhi
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it looks good! Thank you! 🙌
| const isRtlFromStore = useSelector(state => state.locales.isRtl); | ||
| const isRtl = isRtlProp ?? isRtlFromStore; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like we're currently always passing isRtl as a prop, so the hook never actually falls back to the store value. I don't mind keeping this as a fallback, but I wonder if it would make more sense for components to be responsible for reading from the store and passing the value explicitly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes currently we have a double insurance and they do already read from state as a fallback, besides getting passed that value as a prop.
| selectedItemKey={activeTheme} | ||
| isRtl={isRtl} | ||
| depth={2} | ||
| depth={depth + 1} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would happen if depth isn't passed as a prop? We should probably add a default 0 in the component


Aims to resolve UEPR-445
https://scratchfoundation.atlassian.net/browse/UEPR-445
Proposed Changes
To be done additionally concerning nav bar accessibility - in other PRs?
To be discussed additionally
Bugs - currently testing