fix(MobileMenuToggle): trap keyboard focus inside the mobile menu#3911
fix(MobileMenuToggle): trap keyboard focus inside the mobile menu#3911timothyjordan wants to merge 1 commit into
Conversation
When the mobile menu is open, Tab and Shift+Tab now wrap focus between the first and last visible focusable elements inside the surrounding `<nav>` instead of escaping to the underlying page content. Closes withastro#2697
🦋 Changeset detectedLatest commit: cb0c019 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
✅ Deploy Preview for astro-starlight ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Hello! Thank you for opening your first PR to Starlight! ✨ Here’s what will happen next:
|
HiDeoo
left a comment
There was a problem hiding this comment.
Thanks for the contribution 🙌
At first glance, I notice a few potential issues with the current approach:
- The current code would exclude the close button from the focus loop, which feels a bit odd to me when quickly testing it out.
- The manually maintained list of interactive elements could be difficult to maintain. For example, right now it misses
<summary>that we are using for sidebar groups. I think it would also need to be extended to include all possible interactive elements as users may add new ones in their own project. - If we change the viewport width to a point where the mobile menu becomes hidden, e.g. changing the orientation on a tablet or resizing the window, the focus trap would still be active and prevent users from accessing the rest of the page.
Thinking outside of the box, I wonder if we could use the inert attribute to achieve a similar effect in this case. I think we would only need to set inert on the .main-frame and potentially the "Skip to content" link to achieve a similar effect without needing to maintain a list of interactive elements. This could end up being a tiny method called from setExpanded() and a media query listener to handle the viewport width changes.
What do you think? Would you be open to exploring this approach so we can discuss the pros and cons of each approach in more detail?
There was a problem hiding this comment.
We don't really have E2E tests right now for this part of the code or similar interactions. I'm not sure yet if we want to add some or not, but if we do, we would probably add them to the packages/starlight/__e2e__/basics.test.ts file to avoid rebuilding the basics fixture twice.
Maybe until we agree on the approach, we can omit them and add them later when we have a better idea of the final implementation and know for sure that we want to add E2E tests for this part of the code?

Description
Fixes #2697.
When the mobile menu is open on narrow viewports, Tab navigation currently escapes the menu after the last focusable element (the theme switcher) and lands on the underlying page content.
This PR extends the existing
StarlightMenuButtoncustom element with akeydownhandler that wraps Tab / Shift+Tab focus between the first and last visible focusable elements inside the surrounding<nav>while the menu is expanded (body[data-mobile-menu-expanded]).The trap is scoped to the same
<nav>already used for the Escape listener and is gated on the existing data attribute, so there is no behavioural change while the menu is closed.Test plan
packages/starlight/__e2e__/mobile-menu.test.ts:Changeset
Patch bump for
@astrojs/starlightincluded.