Refactor mobile menu to use Popover API - #4121
Conversation
🦋 Changeset detectedLatest commit: c3a8f08 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. |
size-limit report 📦
|
`toggle` fires after the menu has shown/hidden, by using `beforetoggle` we can ensure the changes all happen together
|
Works on Firefox Android as well 👍 |
commit: |
HiDeoo
left a comment
There was a problem hiding this comment.
Amazing that we can finally switch, thanks for taking care of the update 🙌
Left a few comments, but other than that, everything looks great. I tested on:
- macOS: Chrome - Firefox - Safari
- iOS: Safari
- Windows: Chrome - Edge - Firefox
I also played around with some screen readers and the same browsers:
- macOS VoiceOver
- iOS VoiceOver
- Windows NVDA
- Windows JAWS
This is pretty much always equivalent or better like you mentioned and I didn't spot regressions so far.
Totally unrelated to this PR and wouldn't belong here as this is not a new behavior but while testing on Windows (and also noticeable on macOS with scrollbar visibility set to "Always"), we can probably avoid the buttons shift when opening the menu:
shift.mp4
Co-authored-by: HiDeoo <494699+HiDeoo@users.noreply.github.com>
Co-authored-by: HiDeoo <494699+HiDeoo@users.noreply.github.com>
Co-authored-by: HiDeoo <494699+HiDeoo@users.noreply.github.com>
Co-Authored-By: HiDeoo <494699+HiDeoo@users.noreply.github.com>
HiDeoo
left a comment
There was a problem hiding this comment.
I think adding the data attribute changes to the second changeset makes perfect sense.
Thanks for all the updates, this now looks perfect to me and ready 🚀 🧹
There was a problem hiding this comment.
The changes look good to me here. Well done, Chris with the speedy implementation and Hideo with the extraordinary review 🥳
I experimented a bit with the changes in my Sidebar Swipe plugin, but quickly realized that it's not possible to use the popover API there because I need to place the sidebar behind the actual content in order for the Discord-like swipe gesture to work. And AFAIU the intend of popover is... well... being on top of the content, which doesn't go hand in hand with my plugin.
I therefore added the PageFrame.astro as a new component override and "reverted" the changes from here 😥
If someone knows some ingenious way to make it work cleaner, let me know.
EDIT: Chris suggested a potential solution, which would change the behavior a little bit on Discord. 🫶
This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [@astrojs/starlight](https://starlight.astro.build) ([source](https://github.com/withastro/starlight/tree/HEAD/packages/starlight)) | [`0.41.11` → `0.42.0`](https://renovatebot.com/diffs/npm/@astrojs%2fstarlight/0.41.11/0.42.0) |  |  | --- ### Release Notes <details> <summary>withastro/starlight (@​astrojs/starlight)</summary> ### [`v0.42.0`](https://github.com/withastro/starlight/blob/HEAD/packages/starlight/CHANGELOG.md#0420) [Compare Source](https://github.com/withastro/starlight/compare/@astrojs/starlight@0.41.11...@astrojs/starlight@0.42.0) ##### Minor Changes - [#​3572](withastro/starlight#3572) [`292fb17`](withastro/starlight@292fb17) Thanks [@​HiDeoo](https://github.com/HiDeoo)! - Distributes package as JavaScript files with dedicated type declaration files instead of TypeScript source files. - [#​4121](withastro/starlight#4121) [`2623ae6`](withastro/starlight@2623ae6) Thanks [@​delucis](https://github.com/delucis)! - Simplifies markup for Starlight’s mobile menu toggle **⚠️ Potentially breaking change:** If you use a theme plugin, custom styles, or component overrides targeting the `MobileMenuToggle` button or `PageFrame` components, you may need to adjust these for the new markup. The button is no longer wrapped in a `<starlight-menu-button>` custom element and no longer uses the `aria-expanded` attribute. Instead, you can use the `.sl-menu-button` class name to target the button and the `:popover-open` pseudo-class to style the menu open state specifically. In the following example, custom styles for the menu button are updated for the new approach: ```diff - starlight-menu-button button { + .sl-menu-button { color: var(--sl-color-text); } - starlight-menu-button[aria-expanded='true'] button { + .sl-menu-button:has(~ :popover-open) { color: var(--sl-color-text-accent-high); } ``` See [`MobileMenuToggle.astro`](https://github.com/withastro/starlight/blob/main/packages/starlight/components/MobileMenuToggle.astro) and [`PageFrame.astro`](https://github.com/withastro/starlight/blob/main/packages/starlight/components/PageFrame.astro) on GitHub for the full source code of the updated components. - [#​3572](withastro/starlight#3572) [`292fb17`](withastro/starlight@292fb17) Thanks [@​HiDeoo](https://github.com/HiDeoo)! - Removes the `tagline` configuration option, which was never used. If your configuration included a `tagline` option, you can safely remove it without any replacement. - [#​4134](withastro/starlight#4134) [`6135f01`](withastro/starlight@6135f01) Thanks [@​HiDeoo](https://github.com/HiDeoo)! - Updates internal `@astrojs/mdx`, `@astrojs/markdown-satteri`, and `satteri` dependencies.⚠️ **BREAKING CHANGE:** The following minimum versions are now required: - `astro` v7.2.10 or later - `@astrojs/markdown-satteri` 0.4.0 or later (if you use it) - `@astrojs/markdown-remark` 7.3.0 or later (if you use it) Please update Starlight and Astro together: ```sh npx @astrojs/upgrade ``` - [#​4121](withastro/starlight#4121) [`2623ae6`](withastro/starlight@2623ae6) Thanks [@​delucis](https://github.com/delucis)! - Refactors Starlight’s mobile menu toggle to work when JavaScript fails or is disabled⚠️ **BREAKING CHANGE:** This release drops official support for Chromium-based browsers prior to version 116 (released August 2023), Safari-based browsers prior to version 17.0 (released September 2023), and Firefox prior to version 125 (released April 2024). You can find a list of currently supported browsers and their versions using this [browserslist query](https://browsersl.ist/#q=%3E+0.5%25%2C+not+dead%2C+Chrome+%3E%3D+116%2C+Edge+%3E%3D+116%2C+Firefox+%3E%3D+125%2C+Safari+%3E%3D+17.0%2C+iOS+%3E%3D+17.0%2C+not+op_mini+all). This change also removes the `data-mobile-menu-expanded` attribute, which was previously added to `<body>` while the mobile menu is open. If you have custom code that was depending on this attribute, you will need to update it to use a new selector to check if the mobile menu is open. In the following example, a custom background colour for the site header while the menu is open is updated for the new approach: ```diff - [data-mobile-menu-expanded] header { + body:has(sl-sidebar-pane:popover-open) header { background-color: var(--sl-color-bg); } ``` </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC42NS4yIiwidXBkYXRlZEluVmVyIjoiNDQuNjUuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
Starlight 0.42 (PR withastro/starlight#4121) replaced the starlight-menu-button custom element and data-mobile-menu-expanded body attribute with a Popover API toggle (.sl-menu-button) and :popover-open state. Update theme selectors accordingly so the hamburger button and sidebar pane keep their dark/glass styling.

Description
This PR refactors Starlight’s mobile menu toggle to use the Popover API instead of the current JS-powered
<button>witharia-expandedpattern.A small amount of JS remains but it’s now only a progressive enhancement for focus trapping. The core functionality of opening/closing the mobile menu and locking body scroll works even if JS breaks for whatever reason.
The JS is now in the
PageFramecomponent as a custom element that attaches to the popover body instead of the menu button. It reacts to global events (matchMedia()) and the popover’s"toggle"event so it makes more sense to live alongside the element it attaches to. This also means the menu button component is now easier to override as any button with the correctpopovertargetattribute will work.CSS switches from hooking into
[aria-expanded]to using the:popover-openpseudo class.We no longer add a
data-mobile-menu-expandedattribute to<body>when the menu is open and usebody:has(#starlight__sidebar:popover-open)in CSS instead, which works without the JS-managed attribute.While I was updating the button, I switched from
aria-labelto a visually hidden span of text, which is generally the recommended pattern for labels where possible.Browser compatibility
Use of the Popover API requires slightly bumping our minimum supported browsers:
According to browsersl.ist, comparing before and after shows a drop in global coverage of 1 percentage point. The newest minimum supported browser will be Firefox 125, released 29 months ago.
N.B. that umbrella compatibility measures such as
wf-popovershow support only arriving later in some browsers. However, IIUC this is due to subfeatures that we are not relying on not being ready yet and the features we require are safe even in these older browser versions:popoverattributepopovertargetattributehidePopover()method"toggle"eventSafari on iOS had a long-standing bug in versions 17.0–18.2 which prevented tap-away clicks closing popovers as is expected. However, we do not require that behaviour in Starlight mobile menus as there is nowhere a user expects to click away to and the main control is the button.
Accessibility
The button with
popovertargetandpopovercombo has built-in accessible roles equivalent to our previousaria-expandedpattern. I tested with VoiceOver in Firefox, Chrome, and Safari on macOS 26.5.1 and found the behaviour to be a very slight improvement in my opinion compared to the current announcements although both are basically equivalent. Would be great to test in more scenarios!Demo
Here’s a small screen capture of the live Starlight docs and compared to this branch, showing the menu working without JavaScript after these changes:
popover.mp4