Skip to content

ChatSidebar with modal={false} still inert-hides and focus-traps the entire app #246

Description

@einari

Expected

<ChatSidebar modal={false} …> (the default — the prop's own documentation says "a chat lives next to the work, so it should not block it") should leave the rest of the application interactive while open: no focus trap, no scroll lock, and no inert applied outside the panel.

What happens instead

With modal={false}, opening the sidebar makes the entire rest of the page non-interactive — clicks, focus, and text selection are dead everywhere outside the panel until it closes. The backdrop styling is correct (pointer-events: none unless data-modal='true'), so the page looks non-modal, which makes the freeze surprising.

Cause

ChatSidebar (v4.1.0, dist/esm/Chat/ChatSidebar.js:65-68) always renders through react-aria-components' ModalOverlay/Modal. ModalOverlayInner calls useModalOverlay unconditionally whenever open (react-aria-components/dist/private/Modal.js:106-109), and that hook (react-aria/dist/private/overlays/useModalOverlay.js:24-43) unconditionally:

  • runs ariaHideOutside([panel], { shouldUseInert: true }) — walks document.body and sets element.inert = true on every sibling of the panel (ariaHideOutside.js:35,41-49), which is the actual mechanism that freezes the app;
  • traps Tab focus inside the panel (useOverlayFocusContain);
  • locks body scroll (usePreventScroll).

The modal prop only affects backdrop paint/pointer-events and dismissal — it never reaches these react-aria side effects.

Smallest reproduction

const [open, setOpen] = useState(false);
<>
  <button onClick={() => setOpen(o => !o)}>Toggle chat</button>
  <ChatSidebar open={open} onClose={() => setOpen(false)} topics={[]} messages={[]} />
</>

Open the sidebar, then try to click the toggle button (or anything else on the page): every element outside the panel carries inert and ignores the click.

Why it matters

Any consumer following the component's documented default gets a modal in behavior while non-modal in appearance. In Stagehand this presents as "opening the chat blocks the whole app" (Cratis/Stagehand#748) despite the consumer passing nothing but the documented defaults — the failure is invisible in the consumer's own code, which makes it expensive to locate.

Direction (yours to decide)

When modal={false}, render the panel without the Modal/ModalOverlay primitives — a plain positioned element with the same classes/animations — reserving react-aria's overlay machinery for modal={true}. Stagehand is working around it by hand-rolling the slide-in and composing the unwrapped ChatTopicList/ChatConversation pieces.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions