Skip to content

fix(menu): Keep persistent elements interactive while a modal menu is open#1062

Draft
engfragui wants to merge 1 commit into
mainfrom
fix/menu-persist-window-drag-regions
Draft

fix(menu): Keep persistent elements interactive while a modal menu is open#1062
engfragui wants to merge 1 commit into
mainfrom
fix/menu-persist-window-drag-regions

Conversation

@engfragui

@engfragui engfragui commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

⚠️ JUST AN EXPLORATION

Summary

A modal MenuList marks the element tree outside it as inert, which removes those elements from hit-testing. On desktop apps (e.g. Electron) this breaks the native window drag region (-webkit-app-region: drag) of the title bar, since inert elements no longer receive the OS drag gesture.

This change defaults MenuList's getPersistentElements to exempt any element marked with data-reactist-persist from the inert barrier, keeping it interactive while the menu stays modal in every other respect. A consumer-provided getPersistentElements is still honored and merged with the default.

Reference

Usage

Mark the element that should stay interactive (e.g. the draggable title bar):

<div data-reactist-persist style="-webkit-app-region: drag" />

… open

A modal MenuList marks the element tree outside it as inert, which removes
those elements from hit-testing. On desktop apps (e.g. Electron) this breaks
the native window drag region (-webkit-app-region: drag) of the title bar,
since inert elements no longer receive the OS drag gesture.

Default MenuList's getPersistentElements to exempt any element marked with
data-reactist-persist from the inert barrier, keeping it interactive while
the menu stays modal in every other respect. A consumer-provided
getPersistentElements is still honored and merged with the default.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@engfragui engfragui self-assigned this Jun 10, 2026
@engfragui engfragui changed the title fix(menu): keep persistent elements interactive while a modal menu is open fix(menu): Keep persistent elements interactive while a modal menu is open Jun 10, 2026

@doistbot doistbot left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This PR enables elements like native OS title bars to remain interactive while a modal MenuList is open by exempting them from the inert barrier.

Few things worth tightening:

  • Merging the default selector changes the Ariakit getPersistentElements contract to be append-only, preventing callers from opting back into strict modal behavior—consider applying the default only when unset or providing a custom Reactist prop.
  • Running a document-wide querySelectorAll whenever persistent elements resolve adds unnecessary overhead to a common interaction path—this scan should ideally be cached or gated behind an explicit opt-in.

I also included a few optional follow-up notes in the details below.

Optional follow-up note (1)
  • [P3] src/menu/menu.test.tsx:429: The implementation successfully merges getPersistentElements with getPersistentElementsDefault(). However, this test doesn't verify the merge because it lacks a data-reactist-persist element. If the code mistakenly overwrote the defaults instead of merging them, this test would still pass. Consider adding a <div data-testid="default-persist" data-reactist-persist /> to this DOM and asserting that it also avoids the aria-hidden attribute.

Share FeedbackReview Logs

Comment thread src/menu/menu.tsx
const consumerElements = getPersistentElements
? Array.from(getPersistentElements())
: []
return [...getPersistentElementsDefault(), ...consumerElements]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🟡 P2 MenuListProps inherits Ariakit’s getPersistentElements, so callers will expect this prop to define the complete persistent-element set. Merging the default selector here turns it into an append-only hook: once an app adds data-reactist-persist anywhere, every MenuList will keep those elements interactive and a caller can’t opt back into strict modal behavior for a specific menu. That’s a surprising contract change for a pass-through Ariakit prop. Consider applying the default only when getPersistentElements is unset, or introducing a separate Reactist prop for the title-bar escape hatch.

Comment thread src/menu/menu.tsx
const consumerElements = getPersistentElements
? Array.from(getPersistentElements())
: []
return [...getPersistentElementsDefault(), ...consumerElements]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🟡 P2 This makes every modal MenuList pay for document.querySelectorAll('[data-reactist-persist]') whenever Ariakit resolves persistent elements, even in apps that never use this feature. Since menus are a repeated interaction path, walking the whole document and allocating fresh arrays here is avoidable overhead. Please gate the default lookup behind an explicit opt-in or cache the selector results for the lifetime of the open menu instead of rescanning the full DOM on each callback.

@engfragui engfragui marked this pull request as draft June 10, 2026 12:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants