Skip to content

fix(Combobox): make the portalled menu clickable inside a modal dialog (MET-3088) - #66

Merged
Zayooo00 merged 1 commit into
mainfrom
fix/combobox-menu-inside-modal-dialog
Sep 8, 2026
Merged

fix(Combobox): make the portalled menu clickable inside a modal dialog (MET-3088)#66
Zayooo00 merged 1 commit into
mainfrom
fix/combobox-menu-inside-modal-dialog

Conversation

@Zayooo00

@Zayooo00 Zayooo00 commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Problem

A Combobox opened from inside a DialogV2 modal renders its dropdown, but no option can be clicked. Keyboard selection (arrows + Enter) works, which is the tell: the DOM is fine, the pointer never reaches the options.

Regression from #62, where the menu moved to a Headless UI portal.

Cause

With anchor, Headless UI portals ComboboxOptions into <div data-headlessui-portal> under document.body — outside Dialog.Content.

Radix's modal Dialog passes disableOutsidePointerEvents: true, and DismissableLayer responds by setting document.body.style.pointerEvents = "none", restoring auto only on the layer node (the dialog content). pointer-events inherits, so the portalled menu computed to none — it painted normally and ignored every click, which landed on the dialog behind it.

Measured in the app with the menu open:

portalled: true
bodyPE:    "none"
chain:     DIV.<menu> pe=none -> DIV pe=none -> DIV pe=none -> BODY pe=none
insideList: false        // elementFromPoint over an option hit the dialog, not the option

Fix

  1. pointer-events: auto on .positioner, the portalled root. A descendant with auto is hit-testable even when an ancestor is none, so this covers the whole menu subtree and is a no-op outside a dialog.

  2. That alone makes the click reach Radix, which sees a pointerdown outside Dialog.Content and dismisses the dialog mid-selection. So DialogV2 now treats a pointerdown inside [data-headlessui-portal] as inside the dialog and calls preventDefault() — the same lever the existing scrollbar guard uses, since Radix composes the caller's handler ahead of its own and skips dismissal when the event is default-prevented.

Both parts are needed; either alone leaves the interaction broken.

Scope

13 components in the consuming app pair a dialog with a Combobox — settings modals, print modals, breakdown modals, details dialogs. All have had unclickable dropdowns since #62.

Verification

  • npm run build, npx vitest run (8 files, 49 tests), npm run lint — all pass
  • Built artifacts dropped into the app's node_modules and exercised against a local dev build: options select by mouse, and picking one no longer closes the dialog
  • Checked the alternatives before landing this: aria-hidden@1.2.4 is imported as hideOthers, not inertOthers, so nothing is inert and that path was not a factor

…g (MET-3088)

Since the menu moved to a Headless UI portal it renders under `document.body`,
outside the dialog. Radix's modal `Dialog` sets `pointer-events: none` on `body`
while open and restores `auto` only on its own content element, so the menu
inherited `none`: it opened and rendered, but every click passed through to the
dialog behind it. Keyboard selection still worked, which is what gave it away.

Re-enable pointer events on `.positioner`, the portalled root, so the whole menu
subtree is hit-testable again.

That alone lets the click reach Radix, which reads a pointerdown on the menu as
an interaction outside the dialog and closes it mid-selection. So `DialogV2` now
treats a pointerdown inside a Headless UI portal as inside the dialog and
prevents the dismissal.

Affects every dialog that hosts a Combobox — 13 components in the app.
@notion-workspace

Copy link
Copy Markdown

@Zayooo00
Zayooo00 merged commit ca0f999 into main Sep 8, 2026
2 checks passed
@Zayooo00
Zayooo00 deleted the fix/combobox-menu-inside-modal-dialog branch September 8, 2026 12:09
Zayooo00 added a commit to myevaluations/myevals-plasmic-utils that referenced this pull request Sep 8, 2026
…g (MET-3088) (fullstackhouse#66) (#9)

Since the menu moved to a Headless UI portal it renders under `document.body`,
outside the dialog. Radix's modal `Dialog` sets `pointer-events: none` on `body`
while open and restores `auto` only on its own content element, so the menu
inherited `none`: it opened and rendered, but every click passed through to the
dialog behind it. Keyboard selection still worked, which is what gave it away.

Re-enable pointer events on `.positioner`, the portalled root, so the whole menu
subtree is hit-testable again.

That alone lets the click reach Radix, which reads a pointerdown on the menu as
an interaction outside the dialog and closes it mid-selection. So `DialogV2` now
treats a pointerdown inside a Headless UI portal as inside the dialog and
prevents the dismissal.

Affects every dialog that hosts a Combobox — 13 components in the app.
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